近況+DroidKaigiの名札生成スクリプトを書いた話や裏話

こちらは ex-mixi Advent Calendar 2017 の6日目の記事です。
プログラミング以外の話が多くなったので、qiitaではなく自分のblogに書きます。
 

 

自分と近況

@wiroha です。mixiでの名前はガジラです。
2010年10月〜2014年1月と在籍し、日記やつぶやき、カレンダー、プロフィール、通知などを担当しました。

ミクシィ卒業後、最近までは女性ターゲットのアプリを担当し、ディレクションやマネジメントなど何でも屋さんでした。
複数のプロダクトを持ち、合計100万人以上のユーザへ届く機能を自分の一存で決められる環境*1で、やりがいがあるし思考力も鍛えられました。
 
担当プロダクトは前々回の記事にもある通り譲渡したため、今はエンジニアポジションに戻り、これまた前の記事の通り3年のブランクを埋めるべく勉強しているところです。

まだ仕事面では技術ネタが無いので、DroidKaigiでの話を書きます。

DroidKaigi

DroidKaigiはAndroidエンジニア向けの技術カンファレンスで、2015年4月から年1回開催しています。
私は2015年11月頃、ミクシィ時代の同僚に誘われスタッフにjoinしました。
仕事でも個人でも出来ないことをやって楽しんでいます!

前回、2017の話

今年3月に行われたDroidKaigi 2017では事務局長という役割に任命いただき、進捗管理屋さんをしていました。 
役割に限らず、名札*2やconnpassページやスタッフ用マニュアルを作ったりと、ここでも何でも屋さんです。
名札は手作業での作成は大変なので、テンプレを用意し中身は自動化しました。

実行スクリプトを選択後、名簿csvを選ぶと画像が流し込まれます。

f:id:Gateau:20171206120811g:plain

記事の最後にコードも載せておきます*3。誰かの参考になれば幸いです。

裏話

アイコン送付のなかったスピーカーには、代表のmhidaka画伯によるドロイド君が描かれました。
冗談と思いきや本当に使いました。

f:id:Gateau:20171204153307p:plainf:id:Gateau:20171204153319p:plainf:id:Gateau:20171204165303p:plain

 このアイコンになった方に感想を聞いたところ、「ちゃんと画像送れば良かったです」と言われました。印刷した実行犯は私です。ごめんね!

2018の話

DroidKaigiは現在2018のチケットを販売中です!
2日間で多くのセッションを聞けるのは勿論、交流などにも工夫を凝らしています。
より良い会にするための実験的要素もあり、楽しみでありつつどういう反応をされるか不安もありつつです。
 
実は前回、当日も全体管理(セッションが時間通り進行しているかのチェックや、人が足りない係への応援指示、最終撤収など)をしていたため緊張やトラブルで落ち着かず、楽しみ切れなかった後悔があります。
 
今回は当日の役割は未定ですが、事前準備を入念に行い、当日は気を張りすぎず思いっきり満喫しようと思っています。
たくさんの方々に会えるのを楽しみにしています!
ミクシィグループはプラチナスポンサーです!ありがとうございます!!!
卒業後も勉強会やDroidKaigiのMTGでよくお邪魔してますし、大変お世話になってます!!

今後

在籍当時も社内ハッカソン(Weekend Challenge 2.5の第2回)で優勝をいただいたりしましたが、今もこうしてスタッフをしたり、技術系同人誌を書いたり、Google I/Oに行ったり、イベントやものづくりを楽しんでいます。
今後もたくさんの人に楽しまれる・感謝されるプロダクトを作っていけたら幸せです。
 
Androidや女性向けの会を中心に勉強会に参加しているので、会ったらぜひ絡んでください!!
このアドベントカレンダー作成者のesugitaさん!ぜひまた写真を撮る会に誘ってください!(私信)(懐かしい)
今の会社も3年経ち、良いところがあれば次を考えてもいいと思っているので、興味を持たれた方は焼肉に連れて行ってください!(話題になってたtweet

 
明日はkitaindia-san、カレーじゃなくて日本料理屋の話なんですね!?お願いします!!
 

最後にillustrator script

(function() {
    IMAGE_WIDTH = 77; // 画像が埋め込まれる正方形の1辺の長さ(pixel)
    CURRENT_DIR = String(app.documents[0].fullName).replace(app.documents[0].name, "");
    IMAGE_INDEX = 2; //CSVの何列目に画像ファイルの項目があるか

    var CSVConverter = function() {
        this.lines = [];  // 
        this.groups = []; // イラレ上のグループが入っていく
        this.column_name_list = null; // CSVの1行目の項目リスト
        this.variables = app.activeDocument.variables; // The active (frontmost) document in Illustrator

        // イラレアートボード上のグループをloopし、グループリストに格納する
        for (var i = 0; i < this.variables.length; i++) {
            this.groups.push(this.variables[i].pageItems[0]);
        }
    };

    CSVConverter.prototype = {
        read_csv: function() {
            var csv_path = File.openDialog("CSVファイルを選択してください。");
            if (! csv_path) { return; }

            var csv_file = new File(csv_path);
            if (! csv_file.open("r", "", "")) { return; }

            var lines = csv_file.read().split("\n");

            // CSVを読み込み、カラム名リストと値リストへ格納する
            for (var i = 0, n = lines.length; i < n; i++) {
                var line = lines[i];
                if (! line) { continue; }

                if (i == 0) {
                    this.column_name_list = line.split(",");
                } else {
                    this.lines.push(line.split(","));
                }
            }
            csv_file.close();
        },
        write: function() {
            var group_num = 0; // いくつ目のグループか。描画成功すると次のグループへ。
            
            for (var i = 0; i < this.lines.length; i++) {
                var img_file = this._get_filename(this.lines[i][IMAGE_INDEX]);
                if( img_file == null) { continue; } // 画像がない場合は次の人へ

                // 画像がある場合のみテキストの描画 
                var textFrame = this.groups[group_num].textFrames[0]; // テキスト表示領域(今回は1つ)
                var text = String(textFrame.contents);
                
                // csvカラム名と、アートボード上のテキストを一致させればloop有効
                for (var key in this.column_name_list) {
                    if (text.indexOf(this.column_name_list[key]) != -1) {
                        textFrame.contents = this.lines[i][key];
                    }
                }

                // 画像描画
                var rect = this.groups[group_num].pathItems[0]; // 画像描画領域(今回は1つ)
                var pItem = activeDocument.placedItems.add();
                pItem.file = img_file;

                // resizeの引数は%を整数で指定する
                var resize_percentage = IMAGE_WIDTH / pItem.width * 100;
                pItem.resize(resize_percentage, resize_percentage);

                // 描画領域に位置を合わせる
                this._createPosition(pItem, rect);

                // マスクして描画してマスクを解除する
                var mask = activeDocument.pathItems.rectangle(rect.top, rect.left, rect.width, rect.height);
                mask.stroke = true;
                mask.filled = true;
                var holder = app.activeDocument.groupItems.add();
                pItem.move(holder, ElementPlacement.PLACEATEND);
                mask.move(holder, ElementPlacement.PLACEATBEGINNING);
                holder.clipped = true;
                rect.remove();
group_num++; } }, _get_filename: function(github_id) { var name_jpg = decodeURI(github_id) + ".jpg" // githubのID + 拡張子 var name_png = decodeURI(github_id) + ".png" // githubのID + 拡張子 var file = new File(CURRENT_DIR + "/img/" + name_jpg); // プロフ画像 if ( file.length < 0 ){ file = new File(CURRENT_DIR + "/img/" + name_png); // プロフ画像 } if (file.length < 0 ){ file = null; } return file; }, _createPosition: function(profile_image, draw_area) { profile_image.left = draw_area.left; profile_image.top = draw_area.top; } }; var converter = new CSVConverter(); converter.read_csv(); converter.write(); })();
 
 

*1:もちろん上長に共有・相談はしますがかなり自由です。

*2:スピーカー・スタッフ向けのみ。一般参加の方向けには「参加者の名は」を使わせていただきました。 https://yoshiko-pg.github.io/your-name/

*3: 参考元サイトの微妙な実装が残っていますが、もうイラレが無い&今回から名札は別の方法で作るのでメンテはしません… https://bulan.co/swings/illustrator_script/