スポンサーサイト

Tags :
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

InDesignでTwitterのAPIにアクセスし、タイムラインを取得してドキュメントに自動レイアウトしてみる

JavaScriptを使ってInDesignでTwitter APIにアクセスし、タイムラインのステータスを取得して、followしている人たちの発言をInDesignのドキュメントに自動でレイアウトしてみます。

TwitterのAPIではfollowしている人たちの発言の他にも、自分の発言のみ、@Repliesなんかを取得出来ますが、今回は『http://twitter.com/home』で表示される『自分がfollowしている人たちの発言』を取り出して、それをアイコン付きで生成したドキュメントにレイアウトして行く様にしてみました。

■参考URL 『Twitter API 仕様書 日本語訳』
http://watcher.moe-nifty.com/memo/docs/twitterAPI.txt

Socketを使ったWebへのアクセスには、以前のエントリで書いた、Basic認証とファイルのダウンロードが出来るスクリプトをそのまま使っています。Basic認証には、『ユーザID:パスワード』の文字列をBase64エンコードして渡す必要がありますが、今回はその部分は省略してあらかじめエンコードした文字列を用意しています。

MacOSXの場合はターミナルで、
php -r 'echo base64_encode("ユーザID:パスワード")."\n";'
とすればBase64エンコードされた文字列を生成出来ます。

スクリプトの実行結果はこんな感じになります(今回は単ページ)。ドキュメントは一から生成するので雛形は必要ありません。配置するアイコン等の画像は全てローカル(デフォルトではデスクトップ)にダウンロードして保存されます。

旬のDTP Boosterさんfollowさせてもらってます。

Webブラウザで見るのと似た様なレイアウトってのも芸が無い気がしますけど。テキストも最低限オーバーフローしないための処理しかしてないので組版なんて呼べる代物ではありませんが…

一応ドキュメントサイズの変更にはある程度追従出来る様な書き方にはなってますが、本当はレイアウトを自動生成する部分をもうちょっとキレイに、と言うか、CSS的な文脈で書ければ良いなと思ったりもしたんですけど今回は全然煮詰められませんでした(相変わらず思い付くままに書き殴ったようなコードです)。何かテンプレートエンジン的な物が用意出来れば良いんでしょうかね。

■動作確認
MacOSX 10.5.6
InDesign CS4
#target InDesign

//HTTPレスポンスを得る
function getHttpResponse(requests) {
	var parseUrl = function(url) {
		var urlObj = {};
		//[url, scheme, slash, host, port, path, query, fragment] via O'REILLY JavaScript: The Good Parts
		var url_re = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
		var m = url_re.exec(url);
		urlObj.host = m[3];
		urlObj.port = m[4] || '80';
		urlObj.path = m[5];
		urlObj.query = (m[6]) ? '?' +  m[6] : '';
		urlObj.frag = (m[7]) ? '#' + m[7] : '';
		return urlObj;
	}
	var urlObj = parseUrl(requests.url);
	var encoding = requests.encoding || 'UTF-8';
	var method = requests.method || 'GET';
	var auth = (requests.basic_auth != undefined) ? 'Authorization: Basic ' + requests.basic_auth + '\r\n' : '';
	var conn = new Socket;
	conn.timeout = 10;
	if (conn.open (urlObj.host + ':' + urlObj.port, encoding)) {
  	conn.write (method + ' /' + urlObj.path + urlObj.query + urlObj.frag + ' HTTP/1.0\r\n'
		+ 'Host: ' + urlObj.host + '\r\n'
		+ 'User-Agent: ' + 'InDesign/6.0' + '(Macintosh; U; Intel Mac OS X 10_5_6; ja-jp)' + '\r\n'
		+ auth
		+ '\r\n');
		var reply = conn.read(999999);
		conn.close();
		return reply; //ヘッダ込みで返す
	} else {
		return conn.error;
	}
}


//Webからファイルをダウンロードする
function downloadFile(url, localFile) {
	var rep = getHttpResponse({
		method: 'GET',
		url: url,
		encoding: 'BINARY',
	});
	if (rep.match(/HTTP.*\d{3}/).toString().indexOf('200') != -1) {
		//レスポンスのヘッダを除去 
		var body = rep.slice(rep.indexOf('\r\n\r\n') + 4);
		//ファイルに書き出し
		var f = new File(localFile);
		if (f.open("w")) {
			f.encoding = 'BINARY';
			f.write(body);
		}
		f.close();
		return f;
	} else {
		return false;	
	}
}


//RGBカラーを作成
function setRgb(cv) {
	return app.activeDocument.colors.add({
		model: ColorModel.process,
		space: ColorSpace.rgb,
		colorValue: cv
	});
}

TextFrame.prototype = { 
	getHeight: function() { return this.geometricBounds[2] - this.geometricBounds[0]; },
	getWidth: function() { return this.geometricBounds[3] - this.geometricBounds[1]; },
}


//初期設定
var id = 'your_twitter_account';
var b64_userpass = 'ZHVtbXk6ZHVtbXk='; //'username:password'をbase64エンコードした文字列(値はダミー)
var downloadPath = '~/Desktop/'; //ダウンロードしたファイルの保存場所
var cnt = 15; //1ページにレイアウトするPOSTの数
var doc = app.documents.add();
doc.documentPreferences.pageWidth = '110mm';
doc.documentPreferences.pageHeight = '150mm';
var fontName1 = app.fonts.item('小塚ゴシック Pro');
var tu = 'pt';
var default_txtSize = 7; //標準の文字サイズ
var default_txtLead = 9; //標準の行送り	
var container_margin = {
	top: 10,
	left: 7,
	right: 7,
	bottom: 3
};
var my_colors = {
	'white': setRgb([255, 255, 255]),
	'background': setRgb([148, 228, 232]),
	'profile': setRgb([218, 255, 130]),
	'link': setRgb([0, 0, 255])
};

var postRects = [];
var textRects = [];
var iconRects = [];
var iconImgs = [];

var p = doc.pages[0];
var p_b = p.bounds;

var logo = {
	url: 'http://assets0.twitter.com/images/twitter_logo_header.png',
	localFile: downloadPath + 'twitter_logo_header.png'
};

//APIでプロファイルを取得
var profile_info = getHttpResponse({
	url: 'http://twitter.com/users/show/' + id + '.json'
});

var profile_json = eval('(' + profile_info.match(/\{.*\}?/) + ')');
var profile_icon = {
	url: profile_json.profile_image_url,
	localFile: downloadPath + profile_json.profile_image_url.replace(/^.*\//, '')
}

//twitter_logo_header.png, profile_iconをダウンロード
var logo_file = downloadFile(logo.url, logo.localFile);
var profile_icon_file = downloadFile(profile_icon.url, profile_icon.localFile);

//APIでtimelineを取得
var timeline = getHttpResponse({
	url: 'http://twitter.com//statuses/friends_timeline.json?page=1',
	basic_auth: b64_userpass
});
timeline.replace('Connection', '');
tlObj = eval('(' + timeline.match(/\[\{.*\}\]?/) + ')');

//背景の矩形
var bgRect = p.textFrames.add({
	geometricBounds: [p_b[0] - 3, p_b[1] - 3, p_b[2] + 3, p_b[3] + 3],
	fillColor: my_colors.background
});
//ヘッダ部分
var logoRect = p.textFrames.add({
	geometricBounds: [p_b[0] + 2, p_b[1] + container_margin.left, p_b[0] + container_margin.top - 1, p_b[1] + 40],
	fillColor: my_colors.background,
	strokeWeight: 0
});
logoRect.place(logo_file);
logoRect.fit(FitOptions.proportionally);
var profile_rect = p.textFrames.add({
	geometricBounds: [p_b[0] + 2, (p_b[3] - p_b[1]) / 2, p_b[0] + container_margin.top - 1, p_b[3] - container_margin.right],
	fillColor: my_colors.profile,
	strokeWeight: 0
});
var pr_gb = profile_rect.geometricBounds;
var profile_icon_rect = p.textFrames.add({
	geometricBounds: [pr_gb[0], pr_gb[1], pr_gb[0] + profile_rect.getHeight(), pr_gb[1] + profile_rect.getHeight()],
	fillColor: my_colors.background,
	strokeWeight: 0
});
profile_icon_rect.place(profile_icon_file);
profile_icon_rect.fit(FitOptions.proportionally);
var title_rect = p.textFrames.add({
	geometricBounds: [pr_gb[0] + 2, profile_icon_rect.geometricBounds[3], pr_gb[2], pr_gb[3]],
	strokeWeight: 0,
	contents: id + '\'s timeline'
});
title_rect.paragraphs[0].properties = {
	pointSize: 9 + tu,
	appliedFont: fontName1,
	fontStyle: 'R',
	justification: 1667591796
};
//全てのPOSTを納める矩形
var container = p.textFrames.add({
	geometricBounds: [p_b[0] + container_margin.top, p_b[1] + container_margin.left, p_b[2] - container_margin.bottom, p_b[3] - container_margin.right],
	fillColor: my_colors.white
});
var container_gb = container.geometricBounds;
var container_h = container_gb[2] - container_gb[0];
var post_h = container_h / cnt; //各POSTの矩形の高さ


//各POSTをレイアウトして行く
for (var i=0; i<cnt; i++) {
	txtSize = default_txtSize;
	txtLead = default_txtLead;
	//JSONオブジェクトから情報を取り出す
	var tl_text = tlObj[i].text.replace(/</g, '<').replace(/>/g, '>').replace(/\r\n/g, ' ').replace(/\r/g, ' ').replace(/\n/g, ' ');
	var tl_name = tlObj[i].user.name;
	//各POSTの矩形
	var pr = postRects[i] = p.textFrames.add({
		geometricBounds: [container_gb[0] + post_h * i, container_gb[1] + 2, container_gb[0] + post_h * i + post_h, container_gb[3] - 2],
		fillColor: my_colors.white
	});
	var pr_gb = pr.geometricBounds;
	//textを納めるフレーム
	var tr = textRects[i] = p.textFrames.add();
	tr.properties = {
		geometricBounds: [pr_gb[0] + 1, pr_gb[1] + post_h - 1, pr_gb[0] + post_h - 1, pr_gb[3]],
		fillColor: my_colors.white,
		contents: tl_name + ' ' + tl_text,
	};
	tr.paragraphs[0].properties = {
		pointSize: txtSize + tu,
		leading: txtLead + tu,
		appliedFont: fontName1,
		fontStyle: 'R',
		kerningMethod: 'メトリクス'
	};
	//name部分のスタイル
	for (var j=0; j<tl_name.length; j++) {
		tr.paragraphs[0].characters[j].properties = {
			fillColor: my_colors.link,
			fontStyle: 'H',
			pointSize: '7.5' + tu
		};
	}
	//テキストがオーバーフローしている場合の処理
	tr_para = tr.paragraphs[0];
	while (tr.overflows && tr_para.horizontalScale > 90) {
		tr_para.horizontalScale--;
	}
	if (tr.overflows) {
		tr_para.horizontalScale = 100;
		tr_para.properties = {
			pointSize:'6' + tu,
			leading: '7' + tu
		};
		while (tr.overflows) {
			tr_para.horizontalScale--;
		}
	}
	//アイコンの情報を格納()
	iconImgs[i] = {
		url: encodeURI(tlObj[i].user.profile_image_url),
		localFile: downloadPath + tlObj[i].user.profile_image_url.replace(/^.*\//, '')
	};
	if (!new File(iconImgs[i].localFile).exists) {
		downloadFile(iconImgs[i].url, iconImgs[i].localFile); //アイコンの画像ををダウンロード
	}
	//アイコン用の矩形
	iconRects[i] = p.textFrames.add({
		geometricBounds: [pr_gb[0] + 1, pr_gb[1], pr_gb[0] + post_h - 1, pr_gb[1] + post_h - 2],
	});
	iconRects[i].place(new File(iconImgs[i].localFile)); //アイコンの画像を配置
	iconRects[i].fit(FitOptions.proportionally);
}
スポンサーサイト

10年前のDTPエキスパートの参考書の中から…

届いてから未開封のまま放ったらかしにしてたDTPエキスパートの更新試験の問題、さずがに手を付けんとマズいと思い、昨日あたりからやり始めてます。なんかどっかのblogで見たネタとか、wikipedia丸写しみたいな問題がありますね。

で、ふと思い立って10年前(99年版)のDTPエキスパートの参考書を引っ張り出して見てみようとしたんですが、中からすっかり忘れてたヘソクリが出て来ました。
dtpexpert99-02.png

dtpexpert99-01.png
あーそういえば2004年の新札発行時に記念に取っとくかと思ってピン札をこの本に挟んでいたんでした。その時保管してた別のお札も一緒に挟んでたみたいです。DTPがらみの本なら処分しないと考えていたんでしょうか。

dtpexpert99-03.png
右上の五百円札は、子供の頃おばあちゃんがお小遣いとしてくれたのを、大事にとっておいたやつです…おばあちゃん…・゚・(ノД`;)・゚・

更新試験の方は、あと50問くらいなので、まあ何とかなるでしょう…
Profile
choco
Author : choco

印刷・製版の現場を経て、広告制作会社でPhotoshopを使ったビジュアル制作を担当。

→現在は車載機器開発ベンダにて、組み込み3Dデータ作成やUIデザインなどを行っています。

Categories
Favorites


Search
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。