Quantcast
Channel: itouhiroはてなブログ
Viewing all articles
Browse latest Browse all 107

ActionScript3.0でフリガナを表示する

$
0
0

Flashで、テキストにふりがなをつけて表示する。画像以外はActionScript3.0で処理する。

f:id:itouhiro:20130514133757p:plain

実際の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
の写真素材を加工した。


ふりがな の位置が漢字の中央にきてなかったり、改行している文字への ふりがな が改行してなかったり、の問題はあるが、なかなかよい感じである。青空文庫のふりがな形式をそのまま表示できているので、応用効きそうだ。







 


Viewing all articles
Browse latest Browse all 107

Trending Articles