Flashで、テキストにふりがなをつけて表示する。画像以外はActionScript3.0で処理する。
実際のFlash。黄色の矢印をクリックすると進む。
ソースは以下。表示するテキストは外部ファイルを読むようにしていたが、Webにswfを置いたときtxtファイルを読み込めなかったので(swfなら読み込めるのになぜだろう)、以下のソースにはテキストの中身を含めている。
Flash CS6でビルドした。Main.flaは
- instance CharA1, CharB1 を配置。
- DocumentClassに 'Main'を指定。
だけしか入ってない。Main.flaと同じフォルダに Main.as (以下のソース)を置いている。以下のソースから CharA1, CharB1 の6行だけコメントアウトするとFlashDevelopでもコンパイル可能。
package { import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.events.ProgressEvent; import flash.filters.ColorMatrixFilter; import flash.geom.Point; import flash.net.URLLoader; import flash.net.URLRequest; import flash.text.engine.EastAsianJustifier; import flash.text.engine.ElementFormat; import flash.text.engine.FontDescription; import flash.text.engine.TextBlock; import flash.text.engine.TextElement; import flash.text.engine.TextLine; import flash.text.engine.TextRotation; import flash.text.StyleSheet; import flash.text.TextField; /** * ... * @author itouhiro */ [SWF(width="640",height="480",backgroundColor="0xdcdcfc",frameRate="30")] public class Main extends Sprite { private var msg:Array; private var preloadText:TextField; private var preloadBottom:Number; private var percentLoaded:Number; private var ld:URLLoader; private var margin:int = 10; private var idx:int = 0; private var charSize:int = 32; private var furiganaSize:int = 12; private var lineSpace:int = 16; private var board:Sprite; private var block:TextBlock; private var tfd:TextField; private var numCharPerLine:int = 24; //1行何文字表示するか private var lastFurigana:Array = []; public function Main():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); // entry point var str:String = 'Loading: 0 %'; //preloader preloadText = new TextField(); preloadText.x = margin; preloadText.y = margin; preloadText.width = stage.stageWidth - margin * 2; preloadText.height = stage.stageHeight - margin * 2; //preloadText.antiAliasType = AntiAliasType.ADVANCED; //for embedded font only preloadText.wordWrap = true; addChild(preloadText); //loadExternalFile("20130513.txt"); //loadExternalFile("http://japaneseinput.web.fc2.com/flash/20130513.txt"); //loadExternalFile("https://sites.google.com/site/itouhiro/2013/20130513.txt"); var str:String = '\ 代助は立ちながら、画巻物《ゑまきもの》を展開《てんかい》した様な、横長《よこなが》の色彩《しきさい》を眺めてゐた。すると、突然|嫂《あによめ》が這入つて来た。$\ a おや、此所《こゝ》に入《い》らつしやるの$\ b 一寸《ちよいと》其所《そこい》らに私《わたくし》の櫛《くし》が落ちて居《ゐ》なくつて?$\ 櫛《くし》は長椅子《ソーフア》の足《あし》の所《ところ》にあつた。$\ b 昨日《きのふ》縫子《ぬひこ》に貸《か》して遣《や》つたら、何所《どこ》かへ失《なく》なして仕舞つたんで、探《さが》しに来《き》たのよ$\ ‥‥$\ b 相変らず茫乎《ぼんやり》してるぢやありませんか$\ a 御父《おとう》さんから御談義を聞《き》かされちまつた$\ b また? 能く叱《しか》られるのね。御帰り匆々、随分気が利かないわね。$\ b 然し貴方《あなた》もあんまり、好《よ》かないわ。些とも御父《おとう》さんの云ふ通りになさらないんだもの$\ a 御父《おとう》さんの前で議論なんかしやしませんよ。万事控え目に大人しくしてゐるんです$\ b だから猶始末が悪《わる》いのよ。何か云ふと、へい/\つて、さうして、些《ちつ》とも云ふ事を聞かないんだもの$\ ‥‥$\ 梅子《うめこ》は代助の方へ向いて、椅子へ腰を卸した。$\ b まあ、御掛《おか》けなさい。少し話し相手になつて上《あ》げるから$\ a 今日《けふ》は妙な半襟《はんえり》を掛けてますね$\ b これ?此間《こないだ》買つたの$\ a 好《い》い色だ$\ b まあ、そんな事は、何《ど》うでも可《い》いから、其所《そこ》へ御掛《おか》けなさいよ$\ a へえ掛《か》けました$\ b 一体《いつたい》今日《けふ》は何を叱《しか》られたんです$\ a 何を叱《しか》られたんだか、あんまり要領を得ない。然し御父《おとう》さんの国家社会の為《ため》に尽すには驚ろいた。$\ a 何でも十八の年《とし》から今日迄《こんにちまで》のべつに尽《つく》してるんだつてね$\ b それだから、あの位に御成りになつたんぢやありませんか$\ a 国家社会の為に尽《つく》して、金《かね》が御父《おとう》さん位儲かるなら、僕も尽《つく》しても好《い》い$\ b だから遊んでないで、御|尽《つく》しなさいな。貴方《あなた》は寐てゐて御金《おかね》を取《と》らうとするから狡猾よ$\ a 御金《おかね》を取らうとした事は、まだ有《あ》りません$\ b 取《と》らうとしなくつても、使《つか》ふから同《おんな》じぢやありませんか$\ a 兄《にい》さんが何《なん》とか云つてましたか$\ b 兄《にい》さんは呆《あき》れてるから、何とも云やしません$\ a 随分猛烈だな。然し御父《おとう》さんより兄《にい》さんの方が偉《えら》いですね$\ b 何《ど》うして。――あら悪《にく》らしい、又あんな御世辞を使つて。$\ b 貴方《あなた》はそれが悪《わる》いのよ。真面目《まじめ》な顔をして他《ひと》を茶化すから$\ a 左様《そん》なもんでせうか$\ b 左様《そん》なもんでせうかつて、他《ひと》の事ぢやあるまいし。少《すこ》しや考へて御覧なさいな$\ a 何《ど》うも此所《こゝ》へ来《く》ると、丸で門野《かどの》と同《おんな》じ様になつちまふから困《こま》る$\ b 門野《かどの》つて何《なん》です$\ a なに宅《うち》にゐる書生ですがね。$\ a 人《ひと》に何か云はれると、屹度|左様《そん》なもんでせうか、とか、左様《さう》でせうか、とか答へるんです$\ b あの人が? 余っ程妙なのね$\ 遊戯終了――夏目漱石 『それから』 http://www.modelpiece.com/view.php?imageNo=20111226181643-64&category=&keyword=&mode=model&modelNo=20 http://www.modelpiece.com/view.php?imageNo=20110525225133-4&category=&keyword=&mode=model&modelNo=04'; //msg = str.split(/\r?\n/g); msg = str.split(/\$/g); gameStart(); } private function loadExternalFile(file:String):void { var req:URLRequest = new URLRequest(file); ld = new URLLoader(); preloadBottom = 0; ld.addEventListener(ProgressEvent.PROGRESS, loadProgressHandler); ld.addEventListener(Event.COMPLETE, loadCompleteHandler); ld.load(req); } private function loadProgressHandler(e:ProgressEvent):void { percentLoaded = Math.round(e.bytesLoaded / e.bytesTotal * 100) + preloadBottom; preloadText.text = 'Loading: ' + percentLoaded + ' %'; } private function loadCompleteHandler(e:Event):void { removeChild(preloadText); var str:String = ld.data; msg = str.split(/\r?\n/g); gameStart(); } private function gameStart():void { //テキストの外枠 board = new Sprite(); board.graphics.beginFill(0x222222); board.graphics.drawRoundRect( 0, //x 0, //y stage.stageWidth - (margin * 2), //w (charSize*2) * 2, //h margin, //ellipse w margin //ellipse h ); board.graphics.endFill(); board.x = margin; board.y = stage.stageHeight - (charSize * 2) * 2 - margin; addChild(board); //三角 var triangle:Sprite = new Sprite(); triangle.graphics.beginFill(16777060,1); triangle.graphics.moveTo(38,0); triangle.graphics.lineTo(19,26); triangle.graphics.lineTo(0,0); triangle.graphics.lineTo(38,0); triangle.graphics.endFill(); triangle.x = stage.stageWidth - (margin * 2) - (charSize * 2); triangle.y = stage.stageHeight - (charSize * 2) ; addChild(triangle); //メッセージ tfd = new TextField(); var css:StyleSheet = new StyleSheet(); css.setStyle('p', { fontSize:'24pt', fontFamily:'MS Mincho, monospace', color:'#EEEEEE', leading: lineSpace} ); css.setStyle('.strong', { fontWeight:'bold', color:'#FFFFFF' } ); css.setStyle('.naration', { color:'#9999ee' } ); tfd.styleSheet = css; tfd.multiline = true; tfd.width = stage.stageWidth - margin * 2; tfd.height = charSize * 3 ; tfd.htmlText = '<p class="naration">遊戯開始――夏目漱石 『それから』</p>'; tfd.x = margin; tfd.y = margin * 1.5; board.addChild(tfd); triangle.addEventListener(MouseEvent.MOUSE_DOWN, msgForwardHandler); trace('start'); } private function msgForwardHandler(e:MouseEvent):void { //trace('click'); displayMsg(msg[idx]); //trace(idx +':' + msg[idx]); if (msg.length - 1 > idx) { idx++; } } private function displayMsg(str:String):void { //前回のフリガナ削除 for (var i:int = 0; i < lastFurigana.length; i++) { board.removeChild(lastFurigana[i]); } lastFurigana = []; //キャラ var chara:String = ''; if (str.match(/^[a-z] /)) { //chara = 'Char' + str.substr(0, 1).toUpperCase + '1'; chara = str.substr(0, 1); str = str.substr(2); } var furigana:Array = divideFurigana(str); str = furigana.shift(); var ss:Array = []; //strs //行分割 if (str.length > numCharPerLine) { while (str.length > numCharPerLine) { ss.push(str.substr(0, numCharPerLine)); //trace('push:' + str.substr(0, numCharPerLine) +', left:'+ str.substr(numCharPerLine)); str = str.substr(numCharPerLine); } ss.push(str); }else { ss[0] = str; } //表示 var gray:Array = [ //.33, .33, .33, 0, 0, //.33, .33, .33, 0, 0, //.33, .33, .33, 0, 0, //.33, .33, .33, 1, 0 .1, .1, .1, 0, 0, .1, .1, .1, 0, 0, .1, .1, .1, 0, 0, .1, .1, .1, 1, 0 ]; var color:Array = [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0 ]; if (chara === 'a') { CharA1.filters = [new ColorMatrixFilter(color)]; CharB1.filters = [new ColorMatrixFilter(gray)]; }else if (chara === 'b') { CharA1.filters = [new ColorMatrixFilter(gray)]; CharB1.filters = [new ColorMatrixFilter(color)]; }else { CharA1.filters = [new ColorMatrixFilter(gray)]; CharB1.filters = [new ColorMatrixFilter(gray)]; } var hText:String = '<p>' + ss.join('<br>') +'</p>'; if ( ! chara) { hText = '<p class="naration">' + ss.join('<br>') +'</p>'; } trace('hText:' + hText); tfd.htmlText = hText; displayFurigana(furigana); } private function displayFurigana(furigana:Array):void { var i:int; for (i = 0; i < furigana.length; i++) { var locate:Point = getLocation(furigana[i][0]); trace('furigana: '+ furigana[i][2] +', '+ furigana[i][1] +', '+ locate); displayLine(furigana[i][2], furigana[i][1], locate); } } private function getLocation(idx:int):Point { var x:int = 0 + margin; var y:int = 0 + margin * 1.5; if (idx > numCharPerLine) { while (idx > numCharPerLine) { y += charSize + (lineSpace*2/4); idx -= numCharPerLine; } } x = (idx + 0.5 ) * (charSize*3/4); //dpi 72 -> 96 return new Point(x, y); } private function displayLine(str:String, numChar:int, locate:Point):void { var width:int = numChar * charSize; if (str.length > numChar) { width += charSize * (str.length - numChar); //locate.x -= furiganaSize; } //trace('furigana width=' + width); var block:TextBlock = new TextBlock( new TextElement( str, new ElementFormat( new FontDescription('MS Mincho, _typewriter', 'normal'), furiganaSize, 0xffffff ) ), null, //tabstop new EastAsianJustifier(), //均等割付 TextRotation.ROTATE_0 //横書き ); var line:TextLine = block.createTextLine(null, width); line.x = locate.x; line.y = locate.y; board.addChild(line); lastFurigana.push(line); } private function divideFurigana(str:String):Array { var furigana:Array = []; var newstr:String = ''; var inFg:Boolean = false; // in Furigana var bufFg:String = ''; // buffer of Furigana var isLastCharKanji:Boolean = false; var idxFg:int; // index of Furigana var idx:int = 0; //idxは非文字をカウントしない。newstrでのindex var i:int = 0; //iはstrを1文字ずつ見ていくためのカウント数値 while (i < str.length) { var c:String = str.charAt(i); var isKanji:Boolean = false; if (c.match(/[〆ヵヶ々〇〻\x{3400}-\x{9FFF}\x{F900}-\x{FAFF}]/)) { isKanji = true; } if (c.match('《')) { inFg = true; } else if (c.match('》')) { inFg = false; furigana.push([idxFg, idx - idxFg, bufFg]); bufFg = ''; } else if (c.match('|')) { idxFg = idx + 1; } else if (isKanji && ( ! isLastCharKanji)) { idxFg= idx; newstr += c; idx++; } else if (inFg) { bufFg += c; } else { newstr += c; idx++; } isLastCharKanji = isKanji; i++; } furigana.unshift(newstr); return furigana; } } }
テキストは 夏目漱石 『それから』
http://www.aozora.gr.jp/cards/000148/card1746.html
の「テキストファイル(ルビあり)」を一部取り出して加工した。ふりがな関連は加工していない。
上で示したソースコードには、改行部分に $\ という記号がついているが、ついていない以下の形式も処理できる。
http://japaneseinput.web.fc2.com/flash/20130513.txt
男と女の画像は、
http://www.modelpiece.com/view.php?imageNo=20111226181643-64&category=&keyword=&mode=model&modelNo=20
http://www.modelpiece.com/view.php?imageNo=20110525225133-4&category=&keyword=&mode=model&modelNo=04
の写真素材を加工した。
ふりがな の位置が漢字の中央にきてなかったり、改行している文字への ふりがな が改行してなかったり、の問題はあるが、なかなかよい感じである。青空文庫のふりがな形式をそのまま表示できているので、応用効きそうだ。