縦書きを実装してみた

はじめに

この記事は東京高専プロコンゼミアドベントカレンダー2021の記事です.
今は山梨大学編入した身ですが,枠が空いているからとせがまれ書くことになりました.

この記事について

以前,電子ペーパーでなろう小説を読めるようにするを書きました. 電子ペーパーを搭載したM5Stack製のガジェットであるM5Paperでなろう小説を読めるようにしようと頑張った記事です.

記事を読んでいただけたら分かる通り,HTMLをレンダリングしてM5Paperに転送しています.

出力する画面はキレイではあるものの,ページ遷移やデータ転送が異様に遅いものとなっています.また,スマホorPCありきでスタンドアロンではありません.生成される画面がスマホorPC依存であり,崩れた際にCSSを調整するのも一苦労でした. 同時にネットワーク固有の問題も少々出てきました.

なので「M5Paperでなろう小説を読めるようにする」を目標にM5Paper上での縦書きレンダリングを試みました.M5Paper上で縦書きレンダリングが実現できれば上記の問題のほとんどは消えるはずです.

結果としてはあとは移植するだけのところまでできました. 全体的な方針と実装についてを書き記そうと思います.

方針

縦書きを愚直に実装したときに起こる問題

縦書きと言われてどのような実装を思い浮かべますか?まず一番最初に, 一文字一文字のグリフをポンポン縦に並べる実装が思いつきますが,これではダメです.

f:id:ynakano1127:20211225205751p:plain

画像のように小文字や記号の表示に違和感がでてしまうことが容易に想像できるかと思います. 画像では「ごちゅうもん」の「ゅ」だったり「データベース」の「ー」で表記ミス(と捉えられるもの)が起きています.「\「ごじゃいまーす\」」のように鉤括弧が入ったりすると最悪です.なろう小説では特にダッシュ(―)が多用される傾向にあるので,対応が必要です.

一般のソフトウェアでの実装

一般のソフトウェアはHarfbuzzというライブラリを使用しています.縦書きだけではなく,合字変換やら方向の指定やらができる超高レイヤのライブラリです.後述のFreeTypeに依存しています.

The current HarfBuzz codebase is versioned 2.x.x and is stable and under active maintenance. This is what is used in latest versions of Firefox, GNOME, ChromeOS, Chrome, LibreOffice, XeTeX, Android, and KDE, among other places.

FirefoxGNOME,ChromeOS,ChromeLibreOffice,XeTeX,KDEはテキストレンダリングでHarfbuzzを使っているらしいです.凄いですね.関係ないですが,GTKはHarfbuzzに依存したPangoをテキストレンダリングに使っている?っぽいです.

M5Paper上でどのように実現するか

上述のHarfbuzzですが,内部でシステムコールを使ってそう(あんまり詳しく調べてない)かつ使うリソースが多そうという理由で諦めました.

Harfbuzzを使わずに文字レンダリングをするには一つレイヤーを下げてFreeTypeを使うしかありません.(フルスクラッチはキツいので) そもそも,M5Paperを製品として発売するタイミングでFreeTypeを移植してくれていたので,これを使うのが妥当です.

ざっくり言うと,FreeTypeはフォントファイルと文字コード(例えばUnicodeの「あ」は「U+3042」)からビットマップを計算するライブラリです.アウトライン形式のフォントファイルを扱うときとかは特に楽ですよね.

基本的に以下のような使い方をします.チュートリアルはこちら

  1. 文字コードからグリフIDを取得する(FT_Get_Char_Index()
  2. グリフを読み出す(FT_Load_Glyph()
  3. ビットマップを計算(FT_Render_Glyph()

フォントファイルの中身では文字コードとは独立したファイル固有のIDを使ってグリフを管理しています.ここではそれをグリフIDと読んでいます.文字コードとグリフIDの対応付けはフォント内のmapテーブルにて行われます.FreeTypeでは文字コードからグリフIDへの変換をする関数(FT_Get_Char_Index())を用意してくれているので,1番で使用しています.

さて,FreeTypeの使用方法が分かりましたが,このまま使ってしまっては横書き用のグリフが読み出されるだけです.やるべきことは1番と2番の間で縦書き用グリフIDにすり替えることです.しかし,悲しいことにFreeTypeではそのような関数はありません.2番のLOAD時に利用可能なフラグFT_LOAD_VERTICAL_LAYOUTなるものがあり,一瞬心踊ったのですが,

Load the glyph for vertical text layout. In particular, the advance value in the FT_GlyphSlotRec structure is set to the vertAdvance value of the metrics field.

とのことです.縦書きにしたときの対象の文字が専有する高さが取得できるそうですが,肝心の縦書きグリフは取得できません.

横書きグリフIDから縦書きグリフIDへ変換する魔法の箱を作る必要がありそうです.

縦書きグリフを読み出す

フォントファイルの形式

今まで書き連ねてきた「フォントファイル」には様々な種類があります.アウトラインフォントとビットマップフォントで分けられるのは有名な話です.アウトラインには更にPostScriptフォントとTrueTypeフォントで分けられます.OpenTypeはTrueTypeの発展であり,PostScriptを変換したCFFも組み込むことができます.こちらのPDFが非常に参考になります.

OpenTypeの仕様書はこちら

魔法の箱はOpenTypeで定義されるGSUBを読み出して組み立てていきます.

以下余談

自分はこのTrueType/PostScript/OpenTypeの違いが曖昧でした.M5PaperはTTF形式のものを読み込むと至る所に掲載されており,OTFであるNotoを読み込ませた所,エラーを吐いて描画されませんでした.そのため,縦書き不能なTTFのみ描画可能で,縦書き可能なOTFは受け付けないものだと勘違いしてしまいました.

実際は,TrueTypeが入ったOpenTypeファイルとTrueTypeファイルは描画可能,CFF(PostScriptを変換したもの)が入ったOpenTypeは描画不可能なので,縦書きをしつつM5Paperで描画するというのは可能っぽいというのに気づきました.いざNotoについて調べてみるとPostScript形式であると掲載されていました.

フォント形式もPostScriptアウトラインによるOpenTypeとなっている。
Noto-Wikipedia
https://ja.wikipedia.org/wiki/Noto

この記事を書いているときに気づいたので,ちょうど今,記事の書き換えに苦しんでいます.

OpenTypeのテーブル

※これより,こちらの記事が死ぬほど参考になります.いわゆるバイブルってやつです.

GSUBはOpenTypeに定義されるテーブルの一つです.テーブルで有名なのは先程も話に上がった文字コードとグリフIDを変換するcmapでしょうか.簡単なものとしてheadを例に挙げます.

Type Name
uint16 majorVersion
Fixed fontRevision
uint32 checksumAdjustment
uint32 magicNumber
以下略 以下略

定義書を見れば分かりますがこんな感じでデータが型と一緒に定義されます.ビッグエンディアンで配置されているので気をつけましょう.GSUBも同様にデータを順々に読み出していけばグリフIDの変換規則を手に入れることができます.

そもそもheadテーブルやらGSUBテーブルはどのように到達するか.これはファイルの先頭を見れば分かります.テーブル名とオフセットが対応付けられるtableRecordをみると解決です.

TableDirectory:

Type Name Description
uint32 sfntVersion 0x00010000 or 0x4F54544F ('OTTO') — see below.
uint16 numTables Number of tables.
uint16 searchRange Maximum power of 2 less than or equal to numTables, times 16 ((2**floor(log2(numTables))) * 16, where “**” is an exponentiation operator).
uint16 entrySelector Log2 of the maximum power of 2 less than or equal to numTables (log2(searchRange/16), which is equal to floor(log2(numTables))).
uint16 rangeShift numTables times 16, minus searchRange ((numTables * 16) - searchRange).
tableRecord tableRecords[numTables] Table records array—one for each top-level table in the font

TableRecord

Type Name Description
Tag tableTag Table identifier.
uint32 checksum Checksum for this table.
Offset32 offset Offset from beginning of font file.
uint32 length Length of this table.

NotoSansCJKjp-Thin.otf では以下のようなテーブルとそれに対応するオフセットが用意されています.

テーブル名 オフセット(16進数)
BASE e73b84
CFF 45238
DSIG e08920
GPOS e08928
GSUB e1326c
OS/2 170
VORG e337b8
cmap fc0
head 10c
hhea 144
hmtx e33b9c
maxp 168
name 1d0
post 45218
vhea e73b60
vmtx dc8c04

GSUBテーブル

さて,ここからが本題です.GSUBは先程のheadに比べて複雑なのでちゃんとした理解が必要です.なお,ここでは縦書きを実現する機能としてGSUBを紹介していますが,GSUB自体はグリフ置換全般で用いられる仕様です.

GSUBヘッダーについて仕様書にこのようにかかれています.

Type Name Description
uint16 majorVersion Major version of the GSUB table, = 1
uint16 minorVersion Minor version of the GSUB table, = 0
Offset16 scriptListOffset Offset to ScriptList table, from beginning of GSUB table
Offset16 featureListOffset Offset to FeatureList table, from beginning of GSUB table
Offset16 lookupListOffset Offset to LookupList table, from beginning of GSUB table

GSUBですべきことは大きく3工程に分けることができます.

  1. 文字体系と言語を選択
  2. 機能を選択
  3. 文字変換

それぞれ,ScriptListテーブル,FeatureListテーブル,LookupListテーブルを読み込んで行います.GSUBヘッダーにあるscriptListOffsetfeatureListOffsetlookupListOffsetはこれらのテーブルのオフセットです.それぞれGSUBテーブルの先頭からのオフセットであることも仕様書から分かります.

文字体系と言語を選択

1. 文字体系と言語を選択 に関して,ScriptListテーブルに加えて以下のようなテーブルorレコードが定義されています.レコードはテーブルと本質的にあまり変わりません.経験から分かったことですが,テーブルの中に配列として持っているものをRecordと呼ぶそうです.実際,ScriptListの最後にScriptRecordが配列としてありますし,ScriptのなかにLangSysRecordが配列としてあります.

  1. ScriptList table
  2. ScriptRecord
  3. Script table
  4. LangSysRecord
  5. LangSys table

以下テーブルの定義です.

ScriptList table

Type Name Description
uint16 scriptCount Number of ScriptRecords
ScriptRecord scriptRecords[scriptCount] Array of ScriptRecords, listed alphabetically by script tag

ScriptRecord

Type Name Description
Tag scriptTag 4-byte script tag identifier
Offset16 scriptOffset Offset to Script table, from beginning of ScriptList

Script table

Type Name Description
Offset16 defaultLangSysOffset Offset to default LangSys table, from beginning of Script table — may be NULL
uint16 langSysCount Number of LangSysRecords for this script — excluding the default LangSys
LangSysRecord langSysRecords[langSysCount] Array of LangSysRecords, listed alphabetically by LangSys tag

LangSysRecord

Type Name Description
Tag langSysTag 4-byte LangSysTag identifier
Offset16 langSysOffset Offset to LangSys table, from beginning of Script table

LangSys table

Type Name Description
Offset16 lookupOrderOffset = NULL (reserved for an offset to a reordering table)
uint16 requiredFeatureIndex Index of a feature required for this language system; if no required features = 0xFFFF
uint16 featureIndexCount Number of feature index values for this language system — excludes the required feature
uint16 featureIndices[featureIndexCount] Array of indices into the FeatureList, in arbitrary order

機能を選択

2. 機能を選択 に関してはFeatureListテーブルに加えて以下のテーブルorレコードが定義されています.

  1. FeatureList table
  2. FeatureRecord
  3. Feature table

仕様書は以下の通り.

FeatureList table

Type Name Description
uint16 featureCount Number of FeatureRecords in this table
FeatureRecord featureRecords[featureCount] Array of FeatureRecords — zero-based (first feature has FeatureIndex = 0), listed alphabetically by feature tag

FeatureRecord

Type Name Description
Tag featureTag 4-byte feature identification tag
Offset16 featureOffset Offset to Feature table, from beginning of FeatureList

Feature table

Type Name Description
Offset16 featureParamsOffset Offset from start of Feature table to FeatureParams table, if defined for the feature and present, else NULL
uint16 lookupIndexCount Number of LookupList indices for this feature
uint16 lookupListIndices[lookupIndexCount] Array of indices into the LookupList — zero-based (first lookup is LookupListIndex = 0)

文字変換

3. 文字変換 に関してはLookupListテーブルに加えて以下のテーブルorレコードが定義されています.

  1. LookupList table
  2. Lookup table
  3. LookupType 1: Single Substitution Subtable
  4. LookupType 2: Multiple Substitution Subtable
  5. LookupType 3: Alternate Substitution Subtable
  6. LookupType 4: Ligature Substitution Subtable
  7. LookupType 5: Contextual Substitution Subtable
  8. LookupType 6: Chained Contexts Substitution Subtable
  9. LookupType 7: Extension Substitution
  10. LookupType 8: Reverse Chaining Contextual Single Substitution Subtable

LookupList table

Type Name Description
uint16 lookupCount Number of lookups in this table
Offset16 lookupOffsets[lookupCount] Array of offsets to Lookup tables, from beginning of LookupList — zero based (first lookup is Lookup index = 0)

Lookup table

Type Name Description
uint16 lookupType Different enumerations for GSUB and GPOS
uint16 lookupFlag Lookup qualifiers
uint16 subTableCount Number of subtables for this lookup
Offset16 subtableOffsets[subTableCount] Array of offsets to lookup subtables, from beginning of Lookup table
uint16 markFilteringSet Index (base 0) into GDEF mark glyph sets structure. This field is only present if the USE_MARK_FILTERING_SET lookup flag is set.

Lookup tableのlookupTypeによって,そのLookup tableが指定するsubtableの構造が変わります.今回は1文字から1文字の変換さえできれば良いので,LookupType 1: Single Substitution Subtableのみ紹介します.これにはフォーマットが2つあります.

LookupType 1: Single Substitution Subtable (format=1)

Type Name Description
uint16 substFormat Format identifier: format = 1
Offset16 coverageOffset Offset to Coverage table, from beginning of substitution subtable
int16 deltaGlyphID Add to original glyph ID to get substitute glyph ID

LookupType 1: Single Substitution Subtable (format=2)

Type Name Description
uint16 substFormat Format identifier: format = 2
Offset16 coverageOffset Offset to Coverage table, from beginning of substitution subtable
uint16 glyphCount Number of glyph IDs in the substituteGlyphIDs array
uint16 substituteGlyphIDs[glyphCount] Array of substitute glyph IDs — ordered by Coverage index

変換の際に必要になるテーブルとしてCoverageテーブルがあるので,こちらも乗っけます.これもフォーマットが2つあります.

Coverage Format 1

Type Name Description
uint16 coverageFormat Format identifier — format = 1
uint16 glyphCount Number of glyphs in the glyph array
uint16 glyphArray[glyphCount] Array of glyph IDs — in numerical order

Coverage Format 2

Type Name Description
uint16 coverageFormat Format identifier — format = 2
uint16 rangeCount Number of RangeRecords
RangeRecord rangeRecords[rangeCount] Array of glyph ranges — ordered by startGlyphID

CoverageテーブルにRangeRecordなるデータもありますね.これも別途定義されているので,乗っけます.

RangeRecord

Type Name Description
uint16 startGlyphID First glyph ID in the range
uint16 endGlyphID Last glyph ID in the range
uint16 startCoverageIndex Coverage Index of first glyph ID in range

GSUBテーブル2

とまぁペタペタと仕様書をコピペしたわけですが,これでは仕様書の2番煎じですよね(二番煎じを否定する気は全くありませんが).

実装する上で役立ちそうな「お気持ち」を書き記します.図のデータはNotoのNotoSansCJKjp-Thin.otfです.具体的な値は手打ちによるミスがあるかもしれませんのであしからず.

f:id:ynakano1127:20211225195954p:plain

図のように木構造になっていて必要なデータを求めてぴょんぴょん飛ぶお気持ちです.

  1. GSUBヘッダーからscriptListOffsetを取得して飛ぶ
  2. ScriptList中のScriptRecordを一つづつみる.タグがkana(ひらがな/カタカナ)のものを選ぶ.(無かったらhani(中国語/日本語/韓国語),DFLT(デフォルト)を選ぶといいと思います.)
  3. scriptOffsetを見てScriptへ飛ぶ

ここまでで1. 文字体系と言語を選択のうち文字体系を選択できたことになります.

f:id:ynakano1127:20211225200125p:plain 1. 選んだScriptに格納されているデフォルトのLangSysを取得

もしくは

  1. 選んだScript中のLangSysRecordを見ていき,タグがJAN(日本語)であるものを選択
  2. LangSysを取得

ここまでで1. 文字体系と言語を選択が出来たことになります.

f:id:ynakano1127:20211225200422p:plain

同様にして2. 機能を選択を進めます.

  1. ScriptListと同様にGSUBヘッダからFeatureListのトップに行きます.選んだ言語(LangSys)が持つ置き換え機能(FeatureRecord)を得るためにLangSysのfeatureIndicesでフィルターします.
  2. aalt(複数あるバリエーションからの選択)やらccmp(2つ以上のグリフの合字/分解)やらありますが,ここからvertもしくはvrt2を選択します.vertvrt2の違いは欧文の対応がなされているかなされていないかの違いらしいです.(大参考
  3. featureOffsetをみて飛びます.

ここまでで2. 機能を選択が出来たことになります.

f:id:ynakano1127:20211225200804p:plain

同様にして3. 文字変換 を進めます.やりたいことの本質部分にやっと来れました.Notoにない組み合わせが出てくるのでここからは具体的なデータを図に書きません.

  1. 機能選択と同様にFeatureで取得したlookupListIndicesでlookupListをフィルターします.
  2. Lookupに飛びます.このLookupが置き換えのテーブルです.

f:id:ynakano1127:20211225184643p:plain

Coverageテーブルは書き換え元を表し,Subtableは変換規則を表します.(重要)

具体的に以下の手順でLookupテーブルを使用します.

  1. サブテーブルへ移動
  2. CoverageFormatへ移動
  3. 書き換えたいglyphIDをCoverageテーブルのglyphArray中から探索する.(見つからなければ変換の必要がないとする)
  4. SubtableのdeltaGlyphIDを足し合わせてグリフIDのの変換終了.

上述の通りLookup Type1 SubtableとCoverageにはそれぞれ2種類のフォーマットが仕様として存在します.上ではFormat1,Format1の組み合わせを書きましたが,それらの他に以下のような組み合わせも考えられるわけです.

Lookup Type1 Subtable Coverage
Format 1 Format 2
Format 2 Format 1
Format 2 Format 2

最後に一番下のFormat2×Format2という組み合わせを考えた図を乗っけます.

f:id:ynakano1127:20211225184735p:plain

手順は以下の通り

  1. サブテーブルへ移動
  2. CoverageFormatへ移動
  3. rangeRecordのstartGlyphIDからendGlyphIDまでに対象のグリフIDがあるかを確認.もし範囲内ならindex = glyphID - startGlyphID + startCoverageIndexとしてindexを定義.範囲外ならば置き換え対象ではない.
  4. 3番で求めたindexを使ってsubstituteGlyphID[index]を求める.その値こそが置き換えるグリフIDである.

実装

これを実装します.大参考のページでソースコードを公開してくれているので,これをベースにシークすれば実装可能です.

が,キツイっす.400行に達したあたりで完全に手が止まりました.手続き型の限界を感じて一人ソフトウェア危機を迎えました.

今は令和時代です.先人が頑張った結果として我々には強い味方「オブジェクト指向」があります.使わない手はありませんね.

というわけで,こちらが実装したクラス図です.ソースコードこちら

f:id:ynakano1127:20211225191154p:plain

左上から軽く紹介したいと思います.

  • Tagクラスを実装しました.本来符号なし32bit整数でいいのですが,テストやら動作確認やらするときに文字列として描画してくれたほうがやりやすいので,このようにしました.
  • FontFileというクラスがあります.ただのファイルストリームです.M5Paperに移植したときにファイルストリームが変わってしまうのでアダプターとして用意しました.
  • 左上のFontSeekerというFontFileをラップした便利なクラスを用意してGSUBクラス等に渡しています.ビッグエンディアンの処理とか2バイト分取得,といった処理をかなりの回数するので,このようにしました.このアイデア大参考を真似させていただきました.

まずいちばん最初に,GSUBはScriptList,FeatureList,LookupListクラスのコンストラクタを呼び出し,データとして所有します.それぞれのクラスは同様にして適切なクラスを生成し所有します.各クラスの依存関係はクラス図の通りキレイな木構造になってるのが分かるかと思います.

クラスを使った例が以下のソースコードです.言語体系を選択,言語を選択,機能を選択,そして変換規則の関数が得られます.魔法の箱が完成しました!

FontSeeker fs("NotoSansCJKjp-Thin.otf");
Gsub gsub(fs);

Script script;
for(auto script_record : gsub.getScriptList().getScriptList()){
    cout << "ScriptRecord Tag : " << script_record.getScriptTag().toString() << endl;
    if(script_record.getScriptTag().toString() == "kana")
        script = script_record.getScript();
}

LangSys langsys = script.getDefaultLangSys();
for(auto lang_sys_record : script.getLangSysList()) {
    cout << "LangSysRecord Tag : " << lang_sys_record.getLangSysTag().toString() << endl;
    if(lang_sys_record.getLangSysTag().toString() == "JAN ")
        langsys = lang_sys_record.getLangSys();
}

Feature feature;
vector<FeatureRecord> feature_records = gsub.getFeatureList().getFeatureList();
for(uint16_t i : langsys.getFeatureIndices()){
    FeatureRecord r = feature_records[i];
    cout << "FeatureRecord Tag : " << r.getFeatureTag().toString() << endl;
    if( r.getFeatureTag().toString() == "vrt2")
        feature = r.getFeature();
}

vector<LookupSubtableType1> lst1_list;
vector<Lookup> lookups = gsub.getLookupList().getLookupList();
for(uint16_t i : feature.getLookupListIndices()){
    Lookup l = lookups[i];
    cout << "Lookup Type : " << l.getLookupType() << endl;
    if(l.getLookupType() == 1)
        for(uint16_t offset : l.getSubtableOffsets())
            lst1_list.push_back(LookupSubtableType1(fs, l.getLookupOffset() + offset));
}

lst1_list[0].convertGlyph(0x3042); // 変換の関数

この関数を使わない描画がこちらで, f:id:ynakano1127:20211225194331j:plain アフターがこちら, f:id:ynakano1127:20211225194308j:plain なんということでしょう.あのみすぼらしい描画がこんなに美しく.

GSUBを取得する手段としてFT_OpenType_Validate()なるものがあるそうですが,以下を見る通りあまり評判がよくありません.今回は見送りました.

http://d.hatena.ne.jp/keyword/freetypehttps://project-the-tower2.hatenadiary.org/entry/20100509/1273370298

https://aznote.jakou.com/prog/opentype/21_gsub4.html

おわりに

一つの言語のLookupType1しか実装していないので,ときどき横にならないグリフが現れます.他のテーブルを見て置き換えとかしなきゃいけないのかなぁって思ったり思わなかったり.

最終的にM5Paperの移植までやろうと思ったんだけど時間が足りなかったので暇になったらやろうと思う.あとは移植する「だけ」なので.

M5Paperでなろう小説を読めるようにするっていう目標もあとは,改行処理をしてルビ整えて,禁則処理して,HTTPのインターフェイスを設計してUI作って実装してHTMLパースする「だけ」なのでまぁすぐできるんじゃないでしょうか.「暇になったら」やります.いやーつかれたつかれた.

参考

https://www.iwatafont.co.jp/news/img/about_font.pdf

https://aznote.jakou.com/prog/opentype/index.html

https://docs.microsoft.com/en-us/typography/opentype/spec/

Swayを使ってみる

はじめに

この記事は東京高専プロコンゼミアドベントカレンダーの記事です。

背景

このArch Linuxを構築してもう1年以上経ちました。安定したLinux環境を運用できていることが嬉しい一方、新しい環境に対して少しハードルのようなものを感じてきました。このままではいずれ老害になってしまうと思い、Swayの導入を試みました。

Swayって?

WaylandはX WindowSystemの置き換えでGNOMEKDEがサポートしています。

Sway(SirCmpwn's Wayland window manager)はWaylandのコンポジタのひとつです。似たようなものとしてWestonというものがあるそうです。

また、Swayはi3 window managerとの置き換えでもあるため、今まで書いてきた設定ファイルの使い回しができます。

インストール

sudo pacman -S sway
mkdir .config/sway
cp .config/i3/config .config/sway/config

これだけです。 LightDMからSwayを選択して実行できます。

困ったこと

configファイルのエラー

「置き換えるだけでいい」って言ったくせにエラーが起きました。

このようにしてエラーは潰せますがうまくいってるのかどうかは知りません。

$ diff .config/i3/config .config/sway/config 
5,6c5,6
< new_window pixel 1
< new_float normal
---
> default_border pixel 1
> default_floating_border normal
109d108
<         i3bar_command i3bar -t

xrandrが使えない

そりゃそうなので新しいコマンドを探しました。

情報取得

$ swaymsg -t get_outputs                    
Output eDP-1 'Panasonic Industry Company VVX13T043N00 0x00002300' (focused)
  Current mode: 2560x1440 @ 59.999001 Hz
  Position: 0,1542
  Scale factor: 1.000000
  Subpixel hinting: unknown
  Transform: normal
  Workspace: 4
  Available modes:
    2560x1440 @ 48.000000 Hz
    2560x1440 @ 59.999001 Hz

Output HDMI-A-1 'Eizo Nanao Corporation EV2336W 92891093'
  Current mode: 1920x1080 @ 60.000000 Hz
  Position: 0,0
  Scale factor: 0.700000
  Subpixel hinting: unknown
  Transform: normal
  Workspace: 8
  Available modes:
    720x400 @ 70.082001 Hz
    640x480 @ 59.939999 Hz
    800x600 @ 60.317001 Hz
    1024x768 @ 60.004002 Hz
    1280x720 @ 60.000000 Hz
    1280x960 @ 60.000000 Hz
    1280x1024 @ 60.020000 Hz
    1680x1050 @ 59.882999 Hz
    1920x1080 @ 60.000000 Hz

swaymsg -t get_outputs というコマンドでディスプレイの名前等の情報を取得します。これはxrandrとほとんど同じ感じですね。 ここのeDP-1Output HDMI-A-1をつかって操作します。

画面回転、スケール変更

swaymsg 自体が今動いているswayへメッセージを送るコマンドなのでこれを使います。

$ swaymsg -t command output HDMI-A-1 transform 270
$ swaymsg -t command output HDMI-A-1 scale 0.7

位置変更

ディスプレイの左上をどこに置くかをグローバルな座標で指定します。数値が大きくなるにつれて右下へずれます。スケールを変更している場合はそれに応じたpositionを指定するところに気をつけてください。

# HDMIディスプレイを上に、eDP-1を下にもってくる
# HDMIは1920x1080でscaleが0.7だとすると、1080/0.7≒1542なので
$ swaymsg -t command output HDMI-A-1 pos 0 0    
$ swaymsg -t command output eDP-1 pos 0 1542

日本語入力

最初は動かなかったけれど、configのエラーを直して環境変数を直し、リロードしたら直りました。

おわりに

確かにi3のままではあるし、普通にアプリケーションが動くけど細かいところで違うので使ってて微妙だなと思いました。

nm-appletの表示とかタッチパッドの挙動とか直す気にもなれないし、かといって直さないと不便だし。

3日くらい使ってみて慣れたらこのまま使い続けようかと思います。

Qalculate!のご紹介

はじめのはじめに

この記事はプロコンゼミ(SPC同好会) その2 Advent Calendar 2018の16日目の記事です。

adventar.org

はじめに

ちょっとした計算をすることってあると思います。そんなときどうしますか?

pythonを起動

% python  
Python 3.7.1 (default, Oct 22 2018, 10:41:28) 
[GCC 8.2.1 20180831] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 1+1
2
>>> 

Googleを起動

f:id:ynakano1127:20181216184944p:plain

そんな計算手段の一つとしてQalculate!を紹介します。

公式ページは

Qalculate! - the ultimate desktop calculator

github

Qalculate! · GitHub

Qalculate!とは

Qalculate! is a multi-purpose cross-platform desktop calculator. It is simple to use but provides power and versatility normally reserved for complicated math packages, as well as useful tools for everyday needs (such as currency conversion and percent calculation).

Qalculate! - the ultimate desktop calculator より

要はクラスプロットフォームの電卓ってことです。

インストール

インストールは各ディストリビューションに依存します。

自分はarchlinuxなのでpacmanコマンドを使用します。

sudo pacman -S qalculate-gtk

起動すると見るからに電卓って感じのウィンドウが表示されます。(スクリーンショットgtkのテーマが適用されているので他の環境と違うかと思います)

f:id:ynakano1127:20181216185701p:plain

使い方

単純な計算

使い方はシンプルでボックスの中に計算式を入れれば答えが出てきます。

f:id:ynakano1127:20181216190222p:plain

カッコも当然

f:id:ynakano1127:20181216190434p:plain

定数

定数が組み込まれているので円周率とかネイピア数を打ち込まなくても大丈夫 f:id:ynakano1127:20181216191002p:plain f:id:ynakano1127:20181216190942p:plain

関数

三角関数やlogを計算する関数も用意されています。

sin(π/4)は

f:id:ynakano1127:20181216192132p:plain

基数が2のlog16は

f:id:ynakano1127:20181216192252p:plain

日付計算

いろいろな関数が用意されています。日付型を作成、日付型の計算もできます。

日付型の作成

f:id:ynakano1127:20181216191512p:plain

2018年12月16日の10日後は

f:id:ynakano1127:20181216191611p:plain

2018年12月16日の10日前は

f:id:ynakano1127:20181216191654p:plain

年、月、時間も同様に関数を使用して計算することができます。

行列の計算

行列型の作成

f:id:ynakano1127:20181216192641p:plain

ベクトル型の作成

f:id:ynakano1127:20181216192728p:plain

行列式の計算

f:id:ynakano1127:20181216192858p:plain

次数を求める

f:id:ynakano1127:20181216193041p:plain

他にもランクを求めたり、逆行列を求めたり、2つのベクトルの交点を求めたりできます。

方程式

方程式も解けます。

1元1次方程式を解く

f:id:ynakano1127:20181216193420p:plain

1元3次方程式を解く

f:id:ynakano1127:20181216193743p:plain

連立方程式を解く

以下の画像では

  • x+3y+z=6
  • 3x+10y+z=17
  • 2x+6y+z=11

を解いています。第一引数は連立方程式vectorで第二引数は使用する変数を渡しています。

f:id:ynakano1127:20181216194204p:plain

おわりに

スクリーンショットが多くなってしまいました。上に挙げた例以外でも単位の計算や関数の定義、図形に関する計算、プロットなんかもできます。多機能かつシンプルな電卓はこれ以上のものを知りません。

ぜひ使ってみてください。

(全国高専プログラミングコンテストで)半年間PMをした感想、思ったこと,知見

はじめのはじめに

この記事はプロコンゼミ(SPC同好会) その1 Advent Calendar 2018の16日目の記事です。

adventar.org

はじめに

今年度も、私はプログラミングコンテストに出場しました。自由部門1番の「iBird」です。

http://www.procon.gr.jp/

1年生のときには三重県の鳥羽で、2年生のときには山口県の大島で開催され、今年度は徳島県の阿南でした。それぞれ課題、競技、自由部門として参加したのでコンプリートです。(1年生のときはサブメンバーであったが。)

特に今年の弊ゼミは全ての部門が通り、1年生もそれなりの人数が会場入りしたおかげで30人もの大群で行動しました。しかも、初めてのぶちょーで初めてのPM(プロジェクトマネージャー)だったのでなかなか大変でした。そのお話を少しだけ書きます。ぶちょー就任してからもう8ヶ月、プロコン本番からもう2ヶ月たっているので大分記憶が飛んでいます。あしからず。

プロコンゼミ代表のお仕事

代表としてのお仕事は

  • プロコン全体のミーティング
  • 各部門のリーダー決め
  • 予算決め
  • 細かい作業と細かい作業の割り振り

とかです。正直あまり思い出せません。

プロコンゼミ全体のミーティング、各部門のリーダー決めは年度の最初に終わりますが、予算と細かい作業の話は毎週毎日毎時間のように出てきました。会計の人をはじめ、部員とのコミュニケーションをしないときついです。(超重要)

インフラとか作業環境を整えたり、掃除やらなんやらしなきゃいけないのかもしれないですが、弊ゼミは能動的に活動する人ばかりなので、重い仕事はないと思います。勝手にやってくれるので。

プロジェクトマネージャーのお仕事

4月から10月までの間、作品のことを常に考えてしまいます。この仕事が本当にきつかったです。

3月

イデア出し。自分の部門は本校の別のイベントで出たアイデアを流用しようと考えていたのでそんなに精神を病むことがなかったです。課題部門はなかなか難航だったみたいです。ただ、変に首を突っ込むと共倒れするということは分かっていたのであまり口出しをしないでおきました。

4月

メンバー集め。slackの履歴を見ると4月9日に自由部門のチャンネルを作ってメンバーを集めてますね。自分自身すごい早めに行動していたつもりだったのですが今思えば遅くない程度だったのでどんなに頑張っても早すぎることなんてないみたいですね。

f:id:ynakano1127:20181216172228p:plain

5月

1年生が入ってきました。

この頃から毎日活動してますね。5月はずっと予選提出資料で苦しみました。その頃は自分が予選提出資料を一人で書いていたので先生からの圧力がダイレクトに来たのを覚えています。23時まで先生の部屋に残るのが日常になっているので過去の自分ほんとうにすごいなーという気分です。なんだかんだでこのときから利用者へのヒヤリングや物品購入など済ませているのでなかなか優秀だったのではないでしょうか。

6月

前期中間試験です。予選提出から2週間でどう勉強しろと。テストが終わってから今の開発フローは始まりました。今ある作品の前に何個かリポジトリは作っていたのですがほとんど捨てたも同然です。でもまぁ一回でうまくいく開発ではないのは分かっていたので良い経験になったかと思います。

今の開発フローというのはアジャイルトップダウンです。2つのメインページを作ってから細かい実装を以下のようなフローで実装していく形になります。Issueは日本語に直訳すると「問題点」ですが、タスクリストとして機能しました。

f:id:ynakano1127:20181216180419p:plain

7月

夏休みが始まる前に主力メンバーが1人消えます。バカンス羨ましいです。ブルジョアを見るたび「資本主義 ああ資本主義 資本主義」

彼が担っていたDockerを引き継ぎを自分がしました。データベースのスキーマ構成をして一通りWebAPIを作った記憶があります。同時並行でフロントのタスクを別の人に任せていたのですが、「不安定なAPIに合わせたフロントはどうせ書き換わるのだからやりたくない」と言ってきたのをよく覚えています。アジャイル開発だからしょうがないと当時は思いました。知識が入った今、考えればデータベースとインターフェースはしっかりしたほうがいいと思います。しかし、とりあえず実装しなければその知識は入らないのです。どの程度知識をつけて実装をするのかは難しいところですね。ただ、一つ言えるのは試作品を完成させてからのウォーターフォール開発は強いということです。最優秀賞・内閣総理大臣賞をとった偉大なる先輩はプロコンで出す前に一度文化祭で展示をしたそうです。

8月

完全にメンバーの遅刻が常習化します。自分しかミーティングに参加しない日もありました。以下先生のありがたいお言葉。

f:id:ynakano1127:20181215213508p:plain

開発フローにしたがって実装の毎日です。ただし、土日とお盆はちゃんと休みました。メリハリ大切。

f:id:ynakano1127:20181215212717p:plain

バカンスに行った人が帰ってきたので自分は開発からすこし手を引くことができました。夏休み末はもう時間取れないぞ!!!なんて脅しを受けてデスマしてました。本当に精神に悪い。

自分の青春の夏休みはプロコンで終わりました。バケーションらしいバケーションはのんのんびよりばけーしょんしかなかったです。本当に精神に良い。

nonnontv.com

9月

パンフレット原稿、システム調書の締め切りが月の最初らへんにありました。提出期限30分前に電車の中で確認すると図番号のミスを発見します。先生と電話をしながらテザリング通信で提出しました。

f:id:ynakano1127:20181216181227p:plain

提出後、一転、先生は余裕を見せ始めました。確かに開発が進み、予選資料に近づいているのはそう。だけど、自分の中の信頼度成長曲線は発散しまくりです。issueを作ってpullrequestをapproveする繰り返し。むしろ不安さえ出てくる始末です。ここで入ってくる全期末試験。終わった頃には残り1ヶ月です。

10月

先生の急な仕様変更がきつかったです。ただ、クリアしないと実証実験に入れないのでしょうがなかったのかと思います。結局、8月の最後に予定されていた実証実験はこの月に送られました。休日にslackでタスクを託すのはすごく心苦しかった。

この月は他にもいろいろな事があった気がするのですがよく覚えていません。文化祭のときに部室で「うわぁあと一週間じゃんwww」と発言した記憶は残ってます。

本番4日前

集荷1日前。荷物の梱包をしなければなりません。そして梱包する前に一度、デモンストレーション会場の設営準備練習をしなければなりません。文化祭の振替休日だったこともあり、午前は3人くらいしか居ませんでした。集荷まで残り24時間もないのに課題部門のリーダーがサーバーをいじっていてとても腹が立ちました。午後には人が何人かきてすごく助かったような気がする。 f:id:ynakano1127:20181216155000p:plain

本番3日前

集荷です。発送料の関係で65inchディスプレイ、43inchディスプレイ、A0パネル×2、ディスプレイスタンドをテープで巻いて一つの荷物としました。160cmx58cmx102cmで、三辺合計320cmです。運送会社と相談するときにこの数字がネックになり、集荷の手続きは困難を極めました。発送直前は伝票が印刷できなかったり荷物の準備がギリギリだったりで辛かった。

本番2日前

限界です。飛行機に持ち込むものの整理とかをやったのかな?

本番1日前

出発です。出発予定時刻に起きました。「絶起」ってやつです。でもまぁ、集合時間に間に合ったのでOK。飛行機でも寝ることは許さずpull requestを確認していました。現地着いてからは少しの間自由時間が取れたので罪悪感を感じながらカラオケで少し寝ました。

その後、会場に行って参加者受付をしました。ホテルに戻ってからデモンストレーションとプレゼンを先生と確認しました。n時間ぶりの風呂に入って、寝たのは3時くらいですかね。

本番1日目

寝坊して朝食を食べ損ねました。1400円くらいだったような...。

リーダーは参加者連絡会議に出ます。弊高専教授が前で話して「権力」を感じました。

システムセッティングをします。噂で「セッティングする時間は厳密に決められている。時間より前に荷物に触れてはいけない」とか聞いていました。今年だけかわかりませんが時間より前に周りの高専がやっていたのであまり気に病む必要はなかったようです。

プレゼンを見ます。自由部門が1番で課題部門が2番でした。発表場所が別だったので両方は見れないと思っていたのですが自由部門が終わってすぐ移動したら案外入れました。

それ以降はデモンストレーションの一般公開です。自分のことばっかりで他高専の作品は全く見れませんでした。最初のうちはコミュ症発揮して説明ではない説明になります。ですが、結局慣れなので説明のパターン化がされてだんだん作業のようになってきます。

学生交流会に参加します。例年とは違くて大喜利でした。

本番2日目

寝坊して朝食を食べ損ねました。1400円。あーあ。

デモンストレーションの一般公開中に審査員が回ってきて審査をします。企業の方とプロコンの方が3回に分けてやってきます。それとは別にマニュアル審査の方が来て予定通りの挙動をするのかをチェックします。

問題は多々ありましたが問題なく終わりました。(支離滅裂)

結果発表しておしまいです。敢闘賞でした。どうせならいい結果がほしかったですけどまぁしょうがないです。

ホテルに戻ってグダっていたら阿南高専の方から電話がかかってきます。「ヤマトで受け付けられない大きさの荷物があったのでどうしますか」と

  • 阿南高専の方が一度学校に持って帰って弊高専へ発送
  • 今すぐ会場に取りに来て

の二択が示されました。前者は申し訳ないので後者を選びました。とりあえずホテルに持って来たのはいいけどこれからどうしようみたいな。

本番終了1日後

飛行機が20:30なのでこの日は大半が自由時間の予定でした。荷物がなければ。朝から佐川急便の集荷を待ちました。先生に出会い直接営業所まで運んでもらいました。圧倒的感謝です。取り戻した自由時間でカラオケに行きました。向かいの部屋に後輩たちがいました。オタクの習性を見た。

半年間PMをした感想

感想は疲れた。ですかね。

思ったこと

です。

1つ目は審査員がプログラム自体をあまり重要視しないということです。最近ではソースコードの自動生成や便利なツールやライブラリを使うことによって実現可能な幅が大きくなりました。だからこそ実現可能なものの中でどのようなアイデアを出してどのように売り出すかが重要視されるわけです。確かに純粋な技術を極めたいのなら研究者の論文を読むほうが楽しいですからね。また、UI、UXも見られます。短い審査時間の第一印象はとても大きいようです。どうでもいいですがMakerFairで見た「Windows 10 IoT Core」を利用した作品はとても見栄えが良くて驚きました。Linuxを得意とする弊ゼミは向いていないように思います。今更Windowsで開発はしにくいですが機会があればやってみたいです。

2つめ。アジャイル開発は回してこそ成り立つ手法です。今回のプロコンでこれを上記の開発フローを回せたといえる人は9人のメンバーの内、自分含めて3人くらいだったように思います。振ったタスクを遠慮なく切った(他の人に回した)結果です。なぜ切ったかというと「遅かったから」なんですね。でも、1回目のフローは遅くて当然で、2回目、3回目になると当然早くPullRequestが飛んでくるはずなんです。後々に受け取れるメリットを考えず「今、遅れているから直ちに切ります」という判断をしたのはもったいないことだったと思います。

分かったこともあります。プロコンで出すWeb開発ごときでは3人がちょうどいいのです。実際ものができてしまったので、確実に言えることです。逆にデザイナーや言語能力者が足らなかったです。プレゼンとかPR資料では何もわからず何もできませんでした。結局先生の言う通りにしていけばよいという「先生駆動プレゼン」や「先生駆動PR資料」になってしまいました。

3つめ。車があると便利です。集荷を頼むより実際に持っていって受け付けられるかを確認するほうが早いです。自由時間で自由に行動ができます。ホテル-会場間の運搬も先生に頼りっぱなしでした。来年は運転手として貢献したいと思います。

知見

知見としては何かあるんでしょうがこれといって出すものもありません。必要になったときに使える知識が増えたと思いますが意識して絞り出しても難しいです。

おわりに

限界にならないように頑張ったけど結局の所限界になってしまうことが分かった。 プロコン辛かったけどいい経験でした。若いって素晴らしい。来年は受験勉強で開発には参加しないと思う。

日本語が下手なので読みにくい、伝わらないところがあればご指摘ください。

Linuxのフォント設定

はじめのはじめに

この記事はプロコンゼミ(SPC同好会) その2 Advent Calendar 2018の8日目の記事です。

adventar.org

はじめに

Linuxでフォントをいい感じにしたい!!!Googleが良さげなのだしている!!!

ということでLinux環境でのフォントをNotoにする記事です。

使用している環境は

Notoとは

2012年、我らがGoogleは「Noto」というフォントファミリーを公開。

2014年7月に、中国、日本、韓国のフォントファミリーである「Noto Sans CJK」がバージョン1.000として公開。

2017年3月に「Noto Serif CJK」が同じくバージョン1.000として公開。

世界中の言語をサポートすることを目標

すべての言語に対応したフォントを開発することで"豆腐"が現れることがなくなるようにという意味を込めてNoto(no more tofu)という名称が付けられた

だそうです。ぜひ頑張って欲しいです。(圧倒的信者)( Noto - Wikipedia参照)

fontの種類

フォントはいろいろな企業や団体または個人などから沢山出されています。

そのためフォントの種類というときりがないのですが、一般的なフォントの種類を表す「フォントファミリー」というもので分けることができます。

  • sans-serif
  • serif
  • monospace

のようなものがあります。以下記事参照。

book.studio947.net

フォントの設定

fontconfig

フォントの設定は各アプリケーションからできますが、途方に暮れる作業になるでしょう。なのでフォントファミリーにフォントを割り当てるような設定をします。

では、どのようにしてフォントファミリーにフォントを割り当てるのか。「fontconfig」を使います。

https://www.freedesktop.org/wiki/Software/fontconfig/

fontconfigは主に以下2つの機能をもちます。

  • 新しく入ってきたフォントを検出するなどの内部構成の構築
  • 似たようなフォントを返す

fontconfigはいくつかのコマンドを提供します。設定がうまくいかないときの切り分け手段として有用です。

  • fc-list
  • fc-match
  • fc-cache
  • fc-cat
  • fc-query
  • fc-scan
  • fc-pattern
  • fc-validate

以下参照

qiita.com

実演

割り当てるフォントをインストール(今回はNoto)

sudo pacman -S noto-fonts

fontconfigの設定ファイルを然るべきディレクトリに配置

グローバル設定にする場合は/etc/fonts/local.confに書き込むらしいです。書き込む内容は以下のURLの「Configuration File Format」節を真面目によんで書き込まなければならないのでしょうが自分はArch Linuxのサンプルから持ってきました。適当に編集したのでもしかしたら必要のない設定をしているかもしれません。

https://www.freedesktop.org/software/fontconfig/fontconfig-user.html

フォント設定/サンプル - ArchWiki

vim .config/fontconfig/fonts.conf
-------------------------------
<?xml version='1.0'?>
<!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
<fontconfig>

<!-- Default sans-serif font -->
 <match target="pattern">
   <test qual="any" name="family"><string>sans-serif</string></test>
   <edit name="family" mode="prepend" binding="same"><string>Noto Sans</string>  </edit>
 </match>
 
<!-- Default serif fonts -->
 <match target="pattern">
   <test qual="any" name="family"><string>serif</string></test>
   <edit name="family" mode="prepend" binding="same"><string>Noto Serif</string>  </edit>
 </match>

<!-- Default monospace fonts -->
 <match target="pattern">
   <test qual="any" name="family"><string>monospace</string></test>
   <edit name="family" mode="prepend" binding="same"><string>Noto Sans Mono</string></edit>
 </match>

<!-- Fallback fonts preference order -->
 <alias>
  <family>sans-serif</family>
  <prefer>
   <family>Noto Sans</family>
  </prefer>
 </alias>
 <alias>
  <family>serif</family>
  <prefer>
   <family>Noto Serif</family>
  </prefer>
 </alias>
 <alias>
  <family>monospace</family>
  <prefer>
   <family>Noto Sans Mono</family>
  </prefer>
 </alias>
</fontconfig>
-------------------------------

もし、ある特定のアプリケーションでフォントが適応されない場合はそれぞれの設定ファイルにてフォントファミリーを指定すれば解決します。

vim .config/sakura/sakura.conf
-------------------------------
font=monospace 11
-------------------------------
vim .config/i3/config
------------------------------
font pango:monospace 9
------------------------------

fc-match serifとターミナルで打ったときに設定前と設定後で以下のようになるかと思います。

(設定前)ipam.ttf: "IPAMincho" "Regular"
(設定後)NotoSerif-Regular.ttf: "Noto Serif" "Regular"

おわりに

参考記事

以下の記事を参考にしました。ありがとうオープンソース

Noto - Wikipedia

Fontconfig - Wikipedia

fontconfig

https://www.freedesktop.org/software/fontconfig/fontconfig-user.html

フォント - ArchWiki

フォント設定 - ArchWiki

フォント設定/サンプル - ArchWiki

Google Noto Fonts

Updates – Google Noto Fonts

https://book.studio947.net/article/1634/

LINUX(CentOS)のフォントのあれこれ - Qiita