//縦書きパーサスクリプト（開発中バージョン） - written by non-chang

//☆概要
//	プレーンテキスト情報をもとに、JavaScriptにて縦書き文書を整形します。
//	CSS3の縦書き機能の登場や普及が待ちきれないweb制作者全てに捧げます。逆に言えばそれまでの命。
//	このスクリプトを読みこんだ上で、divにid付きで流し込んだプレーンテキストを指定した初期化メソッドを1行書くことで、縦書きに変換します。
//	原稿用紙枚数換算やソース生成のメソッドも分離したので、汎用的に使えるのではないかと。
//	table二重で厳密にやってるので、使い方によっては重いですがブラウザやフォント依存が回避できます。

//☆バージョン遷移
//	[ver0.252] 某案件で出た様々な修正点を反映。
//	[ver0.251] キーコード取得で左右カーソルキーでページを送れるように。safariが二頁単位になっちまうな……。
//	[ver0.25] Windows IEの問題と改行コードの問題を改善。公開の恥ずかしさが多少緩和されました。
//			Win IEだと、innerHTMLで改行コード取得できない→preで囲むと読む→まあ意味違わないしいいか
//	[ver0.241] ページング時に、ページングフォームにページ数を入力できるように修正。
//			enterキー押すとページ移動するようにするのに戸惑ったり。最初からFirebugで調べてりゃ一発解決だったのになぁ……
//			尚、ページ外な整数だった場合は適切な頁に戻してます。NaNの場合は操作前のページのまま。
//	[ver0.24] JavaScriptクラス化。どでかい修正。
//			メソッド全般の見直し。
//			ページングパーサ機能が追加されました。
//	[ver0.23] API（的に使える）版の見切り発車。<br />
//			名称を「原稿用紙パーサ（仮）」から「縦書きパーサ」にしました。既に原稿用紙に限らなくなってますので。
//	[ver0.22] HTMLコードを出力するモードを追加
//	[ver0.21] 一部文字列を縦書きでも見栄えがするよう、アスキーアート処理で代用。
//	[ver0.2] バグフィックスと作り込み。CSSで印刷時の改ページ対応？
//	[ver0.1] カウント時のパース内容表示／縦書き原稿用紙表示。原稿カウンターから縦書きパーサに改名。
//	[ver0.0] 初期版。InnerHTML多用で禁則付きの原稿用紙カウント。

//☆仕様
//	・Safari/Firefox/WinIEで動作検証中。最終的にもこいつらのみサポート対象かな？
//	・禁則は以下５つ決め打ち→　 」）』。、 
//	・ページングパーサ機能は、1枚のHTML内で1つしか設置できません。

//☆既知の問題点
//	・改行コードはLF前提……Winで相当妖しい動きしてますね。まだまだ青いです。
//	・大量のレンダリングを行うとかなり重い。
//		ver0.24のページングパーサを使う分には初期化自体はそう重くないので、現実味があるかと。

//☆ToDo
//	・Xoopsスキン対応のための改造など。空白行を出力しないようにする設定
//	・軽くする。無駄を省く。駄目なコードを駄目じゃなくする。etc...
//	・禁則文字列くらいカスタムできるようにしたいな。
//		ちょっと処理とメンバ変数の保ち方・参照の仕方が変わって来るので、Ver0.3として実装予定です。
//	・縦書き用フォントとかに対応できないもんかなー。特定文字列だけ画像化してみっかなー。
//	・ルビとか対応したいなぁ……想像するだけで面倒だけど。Ver0.4予定とか言ってみる。鬼が笑う。
//	・AJAX使った動的な読み込みを実装研究中-(OKかな？ でも実装汚い……)。




//【 原稿用紙パーサスクリプト - ヘルパーメソッド(3)】===============
function parseVerticalByRel(tagName){
	//「<p rel="vertical">本文</p>」という書式になっているpタグ全てを縦書きに変換するヘルパーメソッド。
	
	if (!document.getElementsByTagName){ return; }
	var tags = document.getElementsByTagName(tagName);
	var objectCount=0 ;
	for (var i=0; i<tags.length; i++){
		var tag = tags[i];
		document.verticalTexts= new Array() ;
		if (tag.getAttribute("rel") == "vertical"){
			//tag.setAttribute("class","genko");
			//alert(i);
			document.verticalTexts[objectCount]= new nc_verticalPerser(tag.innerHTML);
			document.verticalTexts[objectCount].initialize(15, 30); //35なら合う……でも30でCSS調整で。
			tag.innerHTML=document.verticalTexts[objectCount].render();
		}
	}
}
/*
	if (!document.getElementsByTagName){ return; }
	var anchors = document.getElementsByTagName("a");

	// loop through all anchor tags
	for (var i=0; i<anchors.length; i++){
		var anchor = anchors[i];

		if (anchor.getAttribute("href") && (anchor.getAttribute("rel") == "lightbox")){
			anchor.onclick = function () {showLightbox(this); return false;}
		}
	}

*/



//【 原稿用紙パーサスクリプト - ヘルパーメソッド(1)】===============
function parseGenko(sourceDivId,cols,rows){
	//プレーンテキストを流し込んだdivクラスを指定することで、レンダリング置き換えまでを簡略化します。
	//引数cols（1行の文字数）、rows（1ページの行数）は省略可能です。
	//省略した場合、一般的な400字詰め原稿用紙としてパースします。
	//
	//document.getElementById(sourceDivId).innerHTML = '現在処理中です……';
	var verticalText = new nc_verticalPerser(document.getElementById(sourceDivId).innerHTML);
	verticalText.initialize(cols,rows);
	document.getElementById(sourceDivId).innerHTML = verticalText.render();
}


//【原稿用紙パーサスクリプト - ヘルパーメソッド(2)：ページングパーサー】============
//プレーンテキストを流し込んだdivクラスを指定することで、ページング機能付きの単ページ表示を行います。
//　仕様：クラスをdocumentスコープにロードするため、1つのHTMLの中で1つの縦書きクラスしか生成できません。
//　　　：documentに独自オブジェクト配列仕込めば解決できそうだけど……ちょっと保留中。
//
function parseGenkoWithPagingInit(sourceDivId,cols,rows){
	document.vPaging = new nc_verticalPerser(document.getElementById(sourceDivId).innerHTML);
	document.vPaging.initialize(cols,rows);
	
	
	parseGenkoWithPaging(sourceDivId,0,0,false);
}
function parseGenkoWithPaging(sourceDivId,page,beforePage,safariKeyChecker){
	if(isNaN(page)){
		if(beforePage){
			page=beforePage ;
		}else{
			page=0 ;
		}
	}
	if(page>=document.vPaging.pages){page=document.vPaging.pages-1}
	if(page<0){page=0}
		//http://www.asagaotv.ne.jp/~kawasaki/js/jscripti.html#isnan
		//http://www.fumiononaka.com/TechNotes/Flash/FN0108014.html
		//if(page==NaN || (page=="NaN") || (page==Number.NaN)){page=0}
 	target=document.getElementById(sourceDivId) ;
	tempHTML="" ;
	tempHTML+="<form onsubmit=\"parseGenkoWithPaging('"+sourceDivId+"',document.getElementById('page').value-1,"+page+"); return false ;\" style='margin : 0 ; padding : 0 ;'>" ;
	/*
	tempHTML+="<form onsubmit=\"
		if(isNaN(document.getElementById('page').value-1)){
			parseGenkoWithPaging('"+sourceDivId+"',document.getElementById('page').value-1)
		}else{
			document.getElementById('page').value=page
		}; return false ;\" style='margin : 0 ; padding : 0 ;'>" ;
		*/
	tempHTML+="<input type='text' size='3' id='page' value='"+(page+1)+"'>/"+document.vPaging.pages+"頁<br>";
	// onchange=\"parseGenkoWithPaging('"+sourceDivId+"',this.value-1);\"
	tempHTML+="<input type='hidden' id='divName' value='"+sourceDivId+"'>" ;
	tempHTML+="<table border=0><tr>"; // width=100%
	if((page+1) !=document.vPaging.pages){
		tempHTML+="<td><A HREF=\"javascript:parseGenkoWithPaging('"+sourceDivId+"',"+(page+1)+");\">←次へ</A></td>";
	}
	if(page!=0){
		tempHTML+="<td align=right><A HREF=\"javascript:parseGenkoWithPaging('"+sourceDivId+"',"+(page-1)+");\">前へ→</A></td>";
	}
	tempHTML+="</tr></table>";
	tempHTML+="</form>" ;
	tempHTML+= document.vPaging.renderPage(page);
	target.innerHTML=tempHTML ;
	
	//キーイベントで左右送りができるように。（なぜかsafariのみ2ページ送ってしまうという問題有り。）
	document.onkeydown = function(e){
		//safariKeyChecker==true ;
		var keyCode;
		if(e){ //Ff,Safari
			keyCode = e.which;
		}else if(window && window.event){ //IE,(SafariはこれもOK)
			keyCode = window.event.keyCode;
		}else{//それ以外なら非対応
			keyCode = 0;
		}
		//カーソルキーで操作できるように。：←=37,→=39
		if(keyCode==37){
			parseGenkoWithPaging(sourceDivId,(page+1),page,safariKeyChecker);
		}else if(keyCode==39){
			parseGenkoWithPaging(sourceDivId,(page-1),page,safariKeyChecker);
		}
	}
	//safariKeyChecker==false ;
}
/*
最初false
次回trueで書き戻す

*/




//【 原稿用紙パーサスクリプト - クラス本体 】===============
function nc_verticalPerser(sourceText) {
	
	
	// 【 プロパティの定義 】=========================
	//
	sourceText=sourceText.replace(/\r\n?/g, "\n"); //改行コード%0A=LF(\n)/%0D=CR(\r)を\nに統一 ;
	
	//sourceText=sourceText.replace("\r", "\n");
	
	sourceText=sourceText.replace(/<pre>/i, ""); //IE対策用のpreタグがあれば削除。
	sourceText=sourceText.replace(/<\/pre>/i, ""); 
	
	if(navigator.appName == 'Microsoft Internet Explorer'){
		//Xoopsサーバ+IEで改行判定が崩れる……回避処理
		sourceText=sourceText.replace(/<br>/igm, "\n");  //ブログが勝手に挿入する<br>タグを削除。
		sourceText=sourceText.replace(/<br \/>/igm, "\n");  //ブログが勝手に挿入する<br />タグを削除。
	}else{
		sourceText=sourceText.replace(/<br>/igm, "");  //ブログが勝手に挿入する<br>タグを削除。
		sourceText=sourceText.replace(/<br \/>/igm, "");  //ブログが勝手に挿入する<br />タグを削除。
	}
	this.sourceText= sourceText ;
	
	this.cols = 20 ; //一行の文字数（デフォルト値20）
	this.rows = 20 ; //横方向1ページあたりの文字数（デフォルト値20）
	
	this.cursolMarks = new Array() ; //ページごとの処理カーソル位置を保存する配列。
	this.charactors = 0 ;//initialize後に総文字数が代入されます。
	this.pages = 1 ; //initialize後に総ページ数が代入されます（※最終ページの余り行数を含みません）。
	this.lines = 0 ; //initialize後に総行数が代入されます。
	this.leftoverLines = 0 ; //最終ページの余り行数（initialize時にセット）
	
	
	
	// 【 初期化メソッド 】=========================
	//　ここで行と列設定を与えて基本情報をパースし、全ての処理用カーソル位置をcursolMarksに保存します。
	//　レンダリング後に初期化し直して別レンダリングする事も可能です。
	//
	function initialize( cols, rows ){
		
		if(cols){this.cols = cols ;}
		if(rows){this.rows = rows ;}
		if(navigator.appName != 'Microsoft Internet Explorer'){ this.rows += 1 ; } //win IE以外でrows判定崩れる？補正……。
		this.charactors = this.countStrings(this.sourceText) ;
		
		//【 初期化時パース処理 】===================
		//
		cursolMarkCount=0 ; //処理カーソル保存配列のインデックス
		this.cursolMarks[cursolMarkCount] = 0 ; //最初のページのカーソル位置を保存。
		
		thisstr="";	//今調べ中の文字列
		cursol=0;	//カーソル(thisstrの先頭)
		crlf=0;		//thisstr中に何文字目に改行コードを発見したか
		lines=0 ; //処理中の行。
		
		while(cursol < this.sourceText.length){
			//if(this.pages > 10){alert("【テスト】10ページになったので停止します。"); break;} //デバッグ用。無限ループに悩まされてつい。
			
			//cursolからthis.cols文字分のテキストを取得
			thisstr=this.sourceText.substr(cursol, this.cols);
			
			// 改行コードの位置を取得
			crlf=thisstr.indexOf("\n");
			
			if(crlf==0){
				//1文字目が改行ならスキップ（ええと……何故だ？）
				cursol+=1;
			}else if(crlf>=0){
				//改行があったら改行の次の文字まで追加してカーソル移動
				cursol+=crlf+1;
			}else{
				//改行がなかったら縦文字列分の最後までカーソルを動かす+禁則処理
				cursol+= this.cols ;
				for(i=0 ; i<2 ; i++){
					//禁則文字列があったらそれも数える（とりあえず二文字連続するまで反復処理）
					if(checkKinsoku(this.sourceText.substring(cursol, cursol+1) ) ){
						cursol++;
					}
				}
			}
			
			if(lines >= (this.rows-1)){
				//ページ末尾完了→ページのレンダリング、次ページへ
				this.pages++ ;
				lines=0 ;
				cursolMarkCount++ ;
				this.cursolMarks[cursolMarkCount] = cursol ; //ページのカーソル位置を保存。
				continue;
			}
			lines++ ;
		}
		//【最後のページの処理】
		cursolMarkCount++ ;
		this.cursolMarks[cursolMarkCount] = cursol ; //最後のページのカーソル位置を保存。
		
		this.leftoverLines = lines ;
		this.lines=this.cols*this.pages+this.leftoverLines ;
		
	} nc_verticalPerser.prototype.initialize = initialize ;
	
	
	
	// 【 レンダリングメソッド群 】=========================
	//レンダリング結果はメソッドの返り値として渡されます。
	//
	
	function renderPropaties(){//枚数計算情報のみレンダリング
		var result = "" ;
		result+="<h4>出力結果</h4>" ;
		result+=this.cols+" × "+this.rows+"、"+this.cols*this.rows+"詰め原稿用紙<br>" ;
		if(this.leftoverLines!=0){
			result+="総枚数＝"+(this.pages+1)+"（"+this.pages+"枚＋"+this.leftoverLines+"行）<br>" ;
		}else{
			result+="総枚数＝"+(this.pages+1)+"丁度<br>" ;
		}
		/*以下テスト
			result+="<br><h4>デバッグ出力</h4>" ;
			result +="カーソルマーク.length="+this.cursolMarks.length +"<br>";
			for(i=0 ; this.cursolMarks.length > i ; i++){
				result +="カーソルマーク"+i+"="+this.cursolMarks[i] +"<br>";
			}
		*/
		return result ;
	} nc_verticalPerser.prototype.renderPropaties = renderPropaties ;
	
	function render(mode){//標準レンダリング
		var result = "" ;
		tempLine="" ;
		
		//for(i=0 ; i < this.cursolMarks.length ; i++){//カーソルマークのlengthがpagesと一致してない……。
		for(i=0 ; i < this.pages ; i++){
			if(mode=="withPageNumber"){
				result+="\n\n【"+(i+1)+"ページ目】<br>" ;
			}
			result += this.renderPage(i);
		}
		return result ;
	} nc_verticalPerser.prototype.render = render ;
	
	function renderHTMLSource(){ //HTMLソースとしてレンダリングします。
		var result = "" ;
		tempLine="" ;
		
		//説明および簡易CSS追加
		result="下記のHTMLコードをあなたのホームページにコピーしてご利用ください。\n\n<style type='text/css'>.genko td{padding:3px;vertical-align:top;border:1px dotted #ccc}.noDisplay{color : white ;}</style>\n"+result;
		
		for(i=0 ; i < this.pages ; i++){
			result += this.renderPage(i);
		}
		
		//HTMLソースコードに変換：
		//「&,<,>,",'」の無効化
		result=result.replace(new RegExp("&","g"),"&amp;"); //RegExpのgオプション=全てを置換
		result=result.replace(new RegExp("<","g"),"&lt;");
		result=result.replace(new RegExp(">","g"),"&gt;");
		result=result.replace(new RegExp("\"","g"),"&quot;");
		result=result.replace(new RegExp("\'","g"),"&#039;");
		
		result=result.replace(new RegExp("&lt; /","g"),"&lt;/");//気になった点修正
		
		//result=result.replace(new RegExp("\n","g"),"<br>\n");//改行なさすぎ修正（preタグ採用のため没）
		result= "<pre>"+result+"</pre>" ;//Firefoxで妙なスペースはいるためPreタグで囲ってみてます。
			
		return result ;
	} nc_verticalPerser.prototype.renderHTMLSource = renderHTMLSource ;
	
	
	
	function renderPage(pageNumber){
		//1ページ表示するサブルーチン。
		//※initialize時の走査と重複しています。。どうすっかな？
		//
		
		//alert(pageNumber+"/"+this.pages)
		if(pageNumber >= this.pages){
			//警告処理……ただ、なんかインデックス計算基準がおかしい気がする。
			return "【縦書きパーサ実行時エラー】<br>総ページインデックス"+(this.pages-1)+"（実ページ数"+(this.pages)+"）の原稿ソースに対して、<br>インデックス「"+pageNumber+"（実ページ数"+(this.pages+1)+"）」が要求されました。";
		}
		
		tempPage = new Array() ; //ページアレイ
		
		cursol=this.cursolMarks[pageNumber] ;//その行内でのカーソル位置基準をリセット
		lineCount=0 ;
		
		while(cursol < (this.cursolMarks[pageNumber]+this.cols*this.rows)){ //1ページ内の処理
			tempLine="" ; //行のテキスト。行末でまとめてTD分解してresultHTMLに格納。
			
			//cursolからthis.cols文字分のテキストを取得
			thisstr=this.sourceText.substr(cursol, this.cols);
			
			// 改行コードの位置を取得 --現状LFのみ見てます。
			crlf=thisstr.indexOf("\n");
			
			if(crlf==0){
				//1文字目が改行ならスキップ（ええと……何故だ？）
				cursol+=1;
			}else if(crlf>=0){
				//改行があったら改行の次の文字まで追加してカーソル移動
				tempLine+=this.sourceText.substring(cursol, cursol+crlf);
				cursol+=crlf+1;
			}else{
				//改行がなかったら縦文字列分の最後までカーソルを動かす+禁則処理
				tempLine+=this.sourceText.substring(cursol, cursol+this.cols);
				cursol+= this.cols ;
				
				for(j=0 ; j<2 ; j++){
					//禁則文字列があったらそいつも追加（とりあえず二文字まで）
					if(checkKinsoku(this.sourceText.substring(cursol, cursol+1) ) ){
						//alert(true );
						tempLine+=this.sourceText.substring(cursol, cursol+1);
						cursol++;
					}
				}
			}
			tempPage[lineCount]="";//初期化、to get rid of "undefined"
			tempPage[lineCount]+=renderingLine(tempLine,this.cols) ;
			
			lineCount++ ;
		}//while
		
		//-- -- -- -- -- --
		//逆向きレンダリング処理
		var result="";
		for(var i=this.rows-1 ; i >= 0 ; i--){
			if(i<tempPage.length){
				if(tempPage[i].length>0){//"undefined".length==9
					result+="<td>"+tempPage[i]+"</td>" ;//+i+"<br>"として行番号出してたけど安定してきたようなので削除。
				}
			}else{
				//最終ページの剰余行
				//result+="<td><table cellpadding='0' cellspacing='0'><tr><td class='noDisplay'>□</td></tr></table></td>" ;
				result+="<td><table cellpadding='0' cellspacing='0'><tr><td class='noDisplay'></td></tr></table></td>" ;
			}
		}
		
		if(result.length>0){
			//どこで発生しているのか掴めていない文字列「undefined」を削除。。タコ処理。
			result="<table class='genko' cellpadding='0' cellspacing='0' align='right'><tr>"
				+result
				+"</td></tr></table><br clear='all'><br>";
			//result=result.replace(new RegExp("undefined","g"),"");
		}
		
		return result ;
		
	} nc_verticalPerser.prototype.renderPage = renderPage ;
	
	// 【 サブルーチン群 】=========================
	function countStrings(str){
		//strの総文字数を数える - private関数（借り物ソース）
		crlf=0;
		len=str.length;
		cnt=0;
		while(1){
			crlf=str.indexOf("\n", crlf);
			if(crlf<0)	break;
			cnt=cnt+1;//ここ機種依存してません？+2→+1に変更。
			crlf=crlf+1;//同上
		}
		len=len-cnt;
		return len;
	} nc_verticalPerser.prototype.countStrings = countStrings ;
	
	function checkKinsoku(charctor){
		//禁則チェック（借り物ソース／設定可能なように書き換えればあるいは。）
		if(
			charctor=="\n"
			|| charctor=="」"
			|| charctor=="）"
			|| charctor=="』"
			|| charctor=="。"
			|| charctor=="、"
			|| charctor=="”"
			|| charctor=="\"" //半角に効果あるか未検証。
			//|| charctor==" "
		){
			return true ;
		}else{
			return false
		}
	} nc_verticalPerser.prototype.checkKinsoku = checkKinsoku ;
	
	function IsSingleByteChar(Char) { //【未使用（借り物ソース）】Char が半角英数字記号なら真を返す
		var S = escape(Char);
		if (S == Char){	
			return true ;
		}else if (S.length == 3){
			code = S.charAt(1) + S.charAt(2);
			nm = parseInt(code, 16);
			if (nm > 0 && nm < 127){
				return true;
			}
		}
	} nc_verticalPerser.prototype.IsSingleByteChar = IsSingleByteChar ;
	
	// 【 テスト用関数 】=========================
	function test(){//テスト用結果出力
		alert(
			"縦"+this.cols +
			"/横" + this.rows+
			"/総文字数"+this.charactors+
			"/カーソルマーク配列長"+this.cursolMarks.length
		) ;
	} nc_verticalPerser.prototype.test = test ;
}
