/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and * limitations under the License. * * The Original Code is the AIRNovel. * * The Initial Developer of the Original Code is Famibee. * Portions created by the Initial Developer are Copyright * (C) 2009-2015 the Initial Developer. All Rights Reserved. * * Contributor(s): Famibee (famibee.blog38.fc2.com) * * Alternatively, the contents of this file may be used under the terms * of either the GNU General Public License Version 2 or later * (the "GPL"), or the GNU Lesser General Public License Version 2.1 * or later (the "LGPL"), in which case the provisions of the GPL or * the LGPL are applicable instead of those above. If you wish to allow * use of your version of this file only under the terms of either the * GPL or the LGPL, and not to allow others to use your version of this * file under the terms of the MPL, indicate your decision by deleting * the provisions above and replace them with the notice and other * provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file * under the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package com.fc2.blog38.famibee.AIRNovel { import com.fc2.blog38.famibee.manager.EventListenerMng; import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.Sprite; import flash.display.Stage; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Point; import flash.geom.Rectangle; import flash.text.engine.FontLookup; import flash.text.engine.FontPosture; import flash.text.engine.FontWeight; import flash.text.engine.RenderingMode; import flash.text.engine.TextLine; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; import flash.ui.Mouse; import flash.ui.MouseCursor; import flash.utils.IDataInput; import flash.utils.IDataOutput; import flash.utils.unescapeMultiByte; import flashx.textLayout.compose.IFlowComposer; import flashx.textLayout.compose.TextFlowLine; import flashx.textLayout.container.ContainerController; import flashx.textLayout.conversion.ConversionType; import flashx.textLayout.conversion.TextConverter; import flashx.textLayout.edit.SelectionManager; import flashx.textLayout.edit.SelectionState; import flashx.textLayout.elements.FlowElement; import flashx.textLayout.elements.FlowGroupElement; import flashx.textLayout.elements.InlineGraphicElement; import flashx.textLayout.elements.InlineGraphicElementStatus; import flashx.textLayout.elements.LinkElement; import flashx.textLayout.elements.ParagraphElement; import flashx.textLayout.elements.SpanElement; import flashx.textLayout.elements.TCYElement; import flashx.textLayout.elements.TextFlow; import flashx.textLayout.events.FlowElementMouseEvent; import flashx.textLayout.events.SelectionEvent; import flashx.textLayout.events.StatusChangeEvent; import flashx.textLayout.events.UpdateCompleteEvent; import flashx.textLayout.formats.BlockProgression; import flashx.textLayout.formats.FormatValue; import flashx.textLayout.formats.TextDecoration; /* txtFlw.flowComposer.updateAllControllers();すると、 「縦書きの時だけ」new ContainerController(xxx, ...)した xxxのgraphicsがクリアされるので、TextFlow用Spriteを用意。 ルビは現状、parentにaddChild。TLFではなくTLEなので大丈夫。 */ internal final class TLFSprite extends Sprite { private var txtFlw :TextFlow = null; private var sprFlw :Sprite = new Sprite(); private var pHonbun :ParagraphElement = null; static private const FLOW_HONBUN :String = "本文"; private var mu :XML = ; private const ns :Namespace = mu.namespace(); private var needCompose :Boolean = false; public function doCompose():void {needCompose = true;} private const evlmUpd :EventListenerMng = new EventListenerMng(); private var tategaki :Boolean = false; private var hyph :Hyphenation = null; static private const SPAN_CHHIDE :String = "ChHide"; private var doPutCh :Boolean = false; private var spanPutCh :SpanElement = null; private var hArgPutCh :Object = null; private var vctRuby :Vector. = new Vector.(0, false); private var rbArg :Object = { fontFamily :"" , fontWeight :FontWeight.NORMAL , fontPosture :FontPosture.NORMAL , fontLookup :FontLookup.DEVICE , renderingMode :RenderingMode.CFF , size :12 , color :0xFFFFFF , shift :0 , align :"center" , parentFontSize :24 , parentLen :0 , textFlow :null }; private var leLink :LinkElement = null; private var hArgLink :Object = null; static private const LINK_ADDPOS :String = "Link子追加中"; static private const LINK_DUMMY :String = "Link子dummy"; static private const ELM_ADDPOS :String = "ELM子追加中"; private var hArgSpan :Object = {}; private var ldMng :LoadMng = new LoadMng(); static private var hScopeVal :Object = null; static private var fncSe :Function = null; static private var addEvent :Function = function(hArg:Object):void {}; static private var fncRecText :Function = function (txt:String):void {}; static private var tfHint :TextField = null; private var clickedLink :Boolean = false; /* private var mngSelect :SelectionManager = null; private var _isSelect :Boolean = false; private var _selectStr :String = ""; private var _selectStart:int = -1; private var _selectEnd :int = -1; */ static public function initGlobal($hTag:Object, $hScopeVal:Object, $addEvent:Function, $stage:Stage, $recText:Function):void { fncSe = $hTag.playse; hScopeVal = $hScopeVal; addEvent = $addEvent; fncRecText = $recText; if (Mouse.supportsCursor) { tfHint = new TextField(); tfHint.visible = false; tfHint.autoSize = TextFieldAutoSize.LEFT; tfHint.alpha = 0.7; // tfHint.antiAliasType = flash.text.AntiAliasType.ADVANCED; tfHint.background = true; tfHint.backgroundColor = 0xEFEDDD; tfHint.border = true; tfHint.borderColor = 0xEEEEEE; tfHint.embedFonts = false; tfHint.mouseEnabled = false; tfHint.mouseWheelEnabled = false; tfHint.multiline = true; tfHint.selectable = false; // tfHint.textColor const txfo:TextFormat = new TextFormat(); txfo.leading = - 12 /*txfo.size*/ *0.75; txfo.font = "_ゴシック,_typewriter"; tfHint.defaultTextFormat = txfo; $stage.addChild(tfHint); } if (CmnLib.isRetina) fncTLFXML = procTLFXML; } public function TLFSprite() {super();} final public function init(doc:DisplayObjectContainer, $hyph:Hyphenation):void { doc.addChild(this); this.addChild(sprFlw); hyph = $hyph; refresh(); addEventListener(Event.ENTER_FRAME, enterFrameHandler); } final private function enterFrameHandler(e:Event):void { if (! needCompose) return; txtFlw.flowComposer.updateAllControllers(); needCompose = false; } final private function refresh():void { if (txtFlw) txtFlw.flowComposer.removeAllControllers(); evlmUpd.clear(); txtFlw = TextConverter.importToFlow(mu, TextConverter.TEXT_LAYOUT_FORMAT); /* txtFlw.interactionManager = mngSelect; if (mngSelect) evlmUpd.add(txtFlw, SelectionEvent.SELECTION_CHANGE, selectionEvent); */ evlmUpd.add(txtFlw, UpdateCompleteEvent.UPDATE_COMPLETE, refreshRuby); evlmUpd.add(txtFlw, StatusChangeEvent.INLINE_GRAPHIC_STATUS_CHANGE, refreshInlineGrp); this.removeChild(sprFlw); sprFlw = new Sprite(); this.addChild(sprFlw); txtFlw.flowComposer.addController(new TLFCC( sprFlw, CmnLib.stageW, CmnLib.stageH )); pHonbun = txtFlw.getElementByID(FLOW_HONBUN) as ParagraphElement; if (leLink) leLink = txtFlw.getElementByID(LINK_ADDPOS) as LinkElement; needCompose = true; } final private function refreshRuby(e:UpdateCompleteEvent):void { const len:uint = vctRuby.length; for (var i:uint=0; i.@*; const len:uint = xlf.length(); for (var i:int=0; i要素の分 if (hyph.isOverrow()) return; rbArg.fontFamily = hArgSpan.fontFamily || mu.@fontFamily || rbArg.fontFamily; rbArg.fontWeight = mu.@fontWeight || rbArg.fontWeight; rbArg.fontPosture = mu.@fontPosture || rbArg.fontPosture; rbArg.fontLookup = mu.@fontLookup || rbArg.fontLookup; rbArg.renderingMode = mu.@renderingMode || rbArg.renderingMode; //MainThread.myTrace("setRuby :"+ rbArg.fontFamily +':'+ mu.@fontFamily +'='+ rbArg.fontWeight +'='+ rbArg.fontPosture +'='+ rbArg.fontLookup +'='+ rbArg.fontLookup, "I"); ruby = unescapeMultiByte(ruby); const rb:Ruby = new Ruby(); rb.init( ruby , pHonbun.textLength -2 // 前の一文字なので-1 , parent , tategaki , rbArg ) vctRuby.push(rb); //MainThread.myTrace("ruby chkFont rf:"+ rbArg.fontFamily +' mu:'+ mu.@fontFamily +' sp:'+ hArgSpan.fontFamily +' ruby:'+ ruby, "I"); Hyphenation.chkFont(rbArg.fontFamily, ruby, txtFlw.findLeaf(pHonbun.textLength -2)); //MainThread.myTrace("setRuby 4 mu.text:"+ this.text, "D"); } final public function writeCh(str:String, ruby:String, doUpdate:Boolean, delay:Boolean = true):void { //MainThread.myTrace("writeCh str:"+ str +" ruby:"+ ruby, "D"); if (! str) { if (doUpdate) needCompose = true; return; } if (hyph.isOverrow()) { if (doUpdate) needCompose = true; return; } const a_ruby:Array = ruby.split("|"); const a_ruby_len:uint = a_ruby.length; if (a_ruby_len == 2) { switch (a_ruby[0]) { case "link": if (leLink) throw("[link] 既にリンク区間です。リンクは入れ子に出来ません"); writeChLink(a_ruby, a_ruby_len); if (doUpdate) needCompose = true; return; case "endlink": if (! leLink) throw("[endlink] リンク区間ではありません。[link]でリンク区間を始めて下さい"); if (txtFlw.getElementByID(LINK_DUMMY)) throw("[endlink] リンク区間に文字など要素がありません"); leLink.id = null; leLink = null; hArgLink = null; if (doUpdate) needCompose = true; return; case "span": writeChSpan(a_ruby); return; case "lay": replaceMu(a_ruby[1]); return; } } const span:SpanElement = new SpanElement(); span.text = '\t' // Apache Flex 4.14.0以降で // 空spanの扱いが変わってる件に対応 for (var js:String in hArgSpan) span[js] = hArgSpan[js]; if (! leLink) { addHonbunChild(span); } else { for (var jn:String in hArgLink) span[jn] = hArgLink[jn]; if (leLink.numChildren > 1) { addHonbunChild(span); } else if (txtFlw.getElementByID(LINK_DUMMY)) { leLink.replaceChildren(0, 1, [span]); } else { addHonbunChild(span); } } span.text = '' // Apache Flex 4.14.0以降で // 空spanの扱いが変わってる件に対応 const len:uint = str.length; if (a_ruby_len >= 2) { const fge:FlowGroupElement = (leLink || pHonbun); const aidx_tcy:int = fge.getChildIndex(span); switch (a_ruby[0]) { case "grp": if (a_ruby_len < 3) throw("[graph]・grp記述 引数が足りません"); if ((a_ruby[1] != "") && (txtFlw.getElementByID(a_ruby[1]) != null)) return; // grp_i=1から開始(最後の文字を必ずダミーとするため) var ch_grp:String = ""; // for (var grp_i:uint=1; grp_i= 3)? a_ruby[2]: ""; rbArg.parentLen = 1; rbArg.textFlow = null; setRuby(ruby); if (doUpdate) needCompose = true; return; } } const rbArgBkup:Object = CmnLib.clone(rbArg); rbArgBkup.fontFamily = rbArg.fontFamily; rbArgBkup.align = rbArg.align; if (a_ruby_len >= 2) { rbArg.align = a_ruby[0]; ruby = a_ruby[1]; } if (delay) { if (doPutCh) { span.textAlpha = 0; span.backgroundAlpha = 0; } else { hArgPutCh = { textAlpha: span.textAlpha , backgroundAlpha: span.backgroundAlpha }; } span.id = SPAN_CHHIDE; hyph.addStr(span, str, (ruby != "")); if (! doPutCh) { spanPutCh = txtFlw.getElementByID(SPAN_CHHIDE) as SpanElement; if (spanPutCh == null) return; spanPutCh.id = null; doPutCh = true } } else { hyph.addStr(span, str, (ruby != "")); } rbArg.parentLen = CmnLib.getBytesTotal_code( (str.charCodeAt(0) == 10) ?str.substr(1) :str ) *0.5; // 先頭に\n(code=10)を含む場合があるのでtrim // 全角空白はtrimされない rbArg.textFlow = txtFlw; setRuby(ruby); rbArg = rbArgBkup; if (doUpdate) needCompose = true; } final private function writeChLink(aRuby:Array, len:uint):void { const le:LinkElement = leLink = new LinkElement(); le.id = LINK_ADDPOS; const span:SpanElement = new SpanElement(); span.text = " "; span.id = LINK_DUMMY; leLink.addChild(span); const oJson:Object = CmnLib.parseJson(aRuby[1]); if (oJson.href) le.href = oJson.href; if (oJson.target) le.target = oJson.target; hArgLink = { linkActiveFormat :{color:0x88FF88} , linkHoverFormat :{color:0xFFFF00} , linkNormalFormat :{color:0x8888FF} , color :null , textAlpha :null , textDecoration :null }; // フォーカスがない通常状態 le.linkNormalFormat = { color :(oJson.f_color) ? parseInt(oJson.f_color) : 0x8888FF , textAlpha :(oJson.f_alpha) ? parseFloat(oJson.f_alpha) :1.0 , textDecoration : (CmnLib.argChk_Boolean(oJson, "f_ul", true)) ? TextDecoration.UNDERLINE : TextDecoration.NONE }; if (Mouse.supportsCursor) { // マウスオーバー状態 le.linkHoverFormat = { color :(oJson.hf_color) ? parseInt(oJson.hf_color) : 0xFFFF00 , textAlpha :(oJson.hf_alpha) ? parseFloat(oJson.hf_alpha) :1.0 , textDecoration : (CmnLib.argChk_Boolean(oJson, "hf_ul", false)) ? TextDecoration.UNDERLINE : TextDecoration.NONE //利かないっぽい //ng , backgroundColor :(oJson.hb_color) //ng ?parseInt(oJson.hb_color) :0x8888FF //ok , fontSize :30 //不明 , fontFamily :"Osaka" //不明 , fontStyle :flash.text.engine.FontPosture.ITALIC //ok , textRotation :TextRotation.ROTATE_270 //ok , typographicCase :TLFTypographicCase.UPPERCASE }; } // マウスダウン状態 le.linkActiveFormat = { color :(oJson.af_color) ?parseInt(oJson.af_color) :0x88FF88 , textAlpha :(oJson.af_alpha) ?parseFloat(oJson.af_alpha) :1.0 , textDecoration : (CmnLib.argChk_Boolean(oJson, "af_ul", true)) ?TextDecoration.UNDERLINE :TextDecoration.NONE }; if (oJson.b_alpha) hArgLink.backgroundAlpha = parseFloat(oJson.b_alpha); if (oJson.b_color) hArgLink.backgroundColor = parseInt(oJson.b_color); var edDummy:Sprite = new Sprite(); if (oJson.fn || oJson.label) { addEvent(edDummy, MouseEvent.MOUSE_DOWN, { fn:oJson.fn, label:oJson.label, call:oJson.call , global:oJson.global, arg:oJson.arg }); le.addEventListener(FlowElementMouseEvent.MOUSE_DOWN , function (e:FlowElementMouseEvent):void { edDummy.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_DOWN)); if (Mouse.supportsCursor) clickedLink = true; }); } pHonbun.addChild(le); const clickse_fn:String = oJson.clickse; if (clickse_fn == null) { if (tfHint) le.addEventListener( FlowElementMouseEvent.MOUSE_DOWN , function (e:FlowElementMouseEvent):void { tfHint.visible = false; if (Mouse.supportsCursor) clickedLink = true; } ); } else { const prmClickSe:Object = {fn:clickse_fn, join:false}; const clickse_buf:String = oJson.clicksebuf; if (clickse_buf != null) prmClickSe.buf = clickse_buf; le.addEventListener(FlowElementMouseEvent.MOUSE_DOWN , function (e:FlowElementMouseEvent):void { if (tfHint) tfHint.visible = false; if (Mouse.supportsCursor) clickedLink = true; fncSe(prmClickSe); } ); } if (Mouse.supportsCursor) { if ("onenter" in oJson) { addEvent(edDummy, MouseEvent.ROLL_OVER, {label:oJson.onenter, call:true, global:true, arg:oJson.arg}); } if ("onleave" in oJson) { addEvent(edDummy, MouseEvent.ROLL_OUT, {label:oJson.onleave, call:true, global:true, arg:oJson.arg}); } le.addEventListener( FlowElementMouseEvent.ROLL_OVER , function (e:FlowElementMouseEvent):void { if ("hint" in oJson) onEnter4Hint(oJson.hint, le); edDummy.dispatchEvent(new MouseEvent(MouseEvent.ROLL_OVER)); const enterse_fn:String = oJson.enterse; if (enterse_fn == null) return; const prmEnterSe:Object = {fn:enterse_fn, join:false}; const enterse_buf:String = oJson.entersebuf; if (enterse_buf != null) prmEnterSe.buf = enterse_buf; fncSe(prmEnterSe); } ); le.addEventListener( FlowElementMouseEvent.ROLL_OUT , function (e:FlowElementMouseEvent):void { tfHint.visible = false; edDummy.dispatchEvent(new MouseEvent(MouseEvent.ROLL_OUT)); const leavese_fn:String = oJson.leavese; if (leavese_fn == null) return; const prmLeaveSe:Object = {fn:leavese_fn, join:false}; const leavese_buf:String = oJson.leavesebuf; if (leavese_buf != null) prmLeaveSe.buf = leavese_buf; fncSe(prmLeaveSe); } ); } } final private function addHonbunChild(fe:FlowElement):void { // ブレイクマーク表示中、処理により文字表示されたときの対策 const fge:FlowGroupElement = (leLink || pHonbun); const eb:FlowElement = txtFlw.getElementByID('break'); if (eb) { const ci:int = fge.getChildIndex(eb); fge.replaceChildren(ci, ci +1, [fe]); return; } fge.addChild(fe); } final private function onEnter4Hint(strHint:String, le:LinkElement):void { stage.setChildIndex(tfHint, stage.numChildren -1); tfHint.text = "\n"+ strHint +"\n"; const le_txt:String = le.getText(); const le_txt_len:uint = le_txt.length; for (var le_i:uint=0; le_i.@*; const len:uint = xlf.length(); for (var i:int=0; i 1) { const span:SpanElement = spanPutCh; spanPutCh = span.splitAtPosition(1) as SpanElement; span.textAlpha = hArgPutCh.textAlpha; span.backgroundAlpha = hArgPutCh.backgroundAlpha; span.id = null; return span.text; } spanPutCh.textAlpha = hArgPutCh.textAlpha; spanPutCh.backgroundAlpha = hArgPutCh.backgroundAlpha; spanPutCh.id = null; spanPutCh = spanPutCh.getNextSibling() as SpanElement; if (spanPutCh) return spanPutCh.text; spanPutCh = txtFlw.getElementByID(SPAN_CHHIDE) as SpanElement; if (spanPutCh) return spanPutCh.text; doPutCh = false; return ""; } /* final public function addSelectable():void { mngSelect = mngSelect || new SelectionManager(); txtFlw.interactionManager = mngSelect; _isSelect = false; _selectStr = ""; evlmUpd.add(txtFlw, SelectionEvent.SELECTION_CHANGE, selectionEvent); } final private function selectionEvent(e:SelectionEvent):void { const selstt:SelectionState = e.selectionState; _selectStart = selstt.absoluteStart; _selectEnd = selstt.absoluteEnd; _isSelect = true; _selectStr = txtFlw.getText(_selectStart, _selectEnd); } final public function get isSelect():Boolean {return _isSelect;} final public function get selectStr():String {return _selectStr;} final public function get selectStart():int {return _selectStart;} final public function get selectEnd():int {return _selectEnd;} final public function getChrRect(atmIdx:int):Rectangle { if (! _isSelect) return null; const fc:IFlowComposer = txtFlw.flowComposer; const tfl:TextFlowLine = fc.findLineAtPosition(atmIdx); if (! tfl) return null; const tlh:TextLine = tfl.getTextLine(); if (! tlh) return null; const cc:ContainerController = fc.getControllerAt(0); if (! cc) return null; const cw:Number = cc.compositionWidth; const rct:Rectangle = tlh.getAtomBounds(atmIdx -tlh.textBlockBeginIndex); rct.x = (tlh.x + rct.x +cw)% cw; rct.y += tlh.y; return rct; } */ final public function clearText():void { /* mngSelect = null; _isSelect = false; _selectStr = ""; */ leLink = null; hArgLink = null; hArgSpan = {}; doPutCh = false; const len_r:uint = vctRuby.length; for (var k:uint=0; k 0) vctRuby = new Vector.(0, false); delete mu..ns::p.(@id == FLOW_HONBUN).*; refresh(); if (clickedLink) { Mouse.cursor = MouseCursor.ARROW; Mouse.cursor = MouseCursor.AUTO; clickedLink = false; } hyph.clearText(); } final public function recText(txt:String):void { //MainThread.myTrace("recText sp:"+ hArgSpan.fontFamily +": mu:"+ mu.@fontFamily +": txt:"+ txt +":", "D"); if (hArgSpan.fontFamily) txt = '| 《span|fontFamily="'+ hArgSpan.fontFamily +'"》'+ txt; fncRecText(txt); } final private function refresh_mu():void { mu = TextConverter.export(txtFlw, TextConverter.TEXT_LAYOUT_FORMAT, ConversionType.XML_TYPE) as XML; } final public function getXmlTextFlow():XML {return mu;} // 4 Debug final public function get text():String { refresh_mu(); return mu.toXMLString(); } final public function recordAMF(out:IDataOutput):void { refresh_mu(); TLFSprite.fncRecordAMF(out, this); } final public function playbackAMF(inp:IDataInput):void { clearText(); mu = inp.readObject(); tategaki = (mu.@blockProgression == BlockProgression.RL); rbArg = inp.readObject(); hyph.setFontFamily(mu.@fontFamily); refresh(); TLFSprite.fncPlaybackAMF(inp, this); } static private var fncRecordAMF :Function = recordAMF_notxt; static private var fncPlaybackAMF :Function = playbackAMF_notxt; static public function chgModeRecordTxt(recordTxt:Boolean):void { if (recordTxt) { fncRecordAMF = recordAMF_txt; fncPlaybackAMF = playbackAMF_txt; } else { fncRecordAMF = recordAMF_notxt; fncPlaybackAMF = playbackAMF_notxt; } } static private function recordAMF_notxt(out:IDataOutput, me:TLFSprite):void { const muBkup:XML = me.mu.copy(); delete muBkup.*; muBkup.appendChild(

); // テキストはコピーしない。save時も、copyLay時も。 out.writeObject(muBkup); out.writeObject(me.rbArg); } static private function recordAMF_txt(out:IDataOutput, me:TLFSprite):void { out.writeObject(me.mu); out.writeObject(me.rbArg); const lenVctRuby:uint = me.vctRuby.length; out.writeUnsignedInt(lenVctRuby); for (var i:uint=0; i(lenVctRuby, false); for (var i:uint=0; i