package com.oxylusflash.book { import caurina.transitions.*; import com.oxylusflash.events.*; import com.oxylusflash.utils.*; import flash.display.*; import flash.events.*; import flash.net.*; import flash.printing.*; import flash.utils.*; import org.papervision3d.cameras.*; import org.papervision3d.materials.*; import org.papervision3d.render.*; import org.papervision3d.scenes.*; import org.papervision3d.view.*; import org.papervision3d.view.stats.StatsView; public class BookHolder extends Sprite { private var renderer:BasicRenderEngine; private var camera:Camera3D; public var viewport:Viewport3D; private var scene:Scene3D; private var mousePage3D:BookPage3D; public var pagesHolder:BookPagesHolder; private var mousePageEnd:int = 0; private var w2:Number; private var h2:Number; private var pageLoader:Loader; private var pagesNode:XML; private var pageLoadIndex:int = -1; private var pageTweenParams:Object; private var pagesArray:Array = []; private var mouseHasMoved:Boolean; public var jumpTimer:Timer; private var jumpDir:int; private var _numPages:int = 0; private var _estimPages:int; private var _leftPageIndex:int = -1; private var autoFlip:Boolean = false; private var autoFlipPaused:Boolean = false; private var flipTimer:Timer; private var printJob:PrintJob; private var printJobOptions:PrintJobOptions = new PrintJobOptions(true); private static const MIN_NUM_PAGES_LOAD:int = 2; private static const JUMP_TO_NEXT:int = 1; private static const JUMP_TO_PREV:int = -1; private static const FLIP_RTL:int = -1; private static const FLIP_NONE:int = 0; private static const FLIP_LTR:int = 1; private var crtFlipDir:int = FLIP_NONE; public static const PAGE_VIEW_UPDATE:String = "pageViewUpdate"; public static const FLIP_READY:String = "flipReady"; public static const AUTO_FLIP_TICK:String = "autoFlipTick"; public var pageIds:Array = []; private var currentContent:DisplayObject; private var flipsCount:int; public function BookHolder() { //this.filters = [Global.SHADOW]; // Init 3D scene BitmapMaterial.AUTO_MIP_MAPPING = true; renderer = new BasicRenderEngine(); camera = new Camera3D(); camera.useCulling = true; camera.useClipping = true; camera.z = -Math.abs(camera.z) camera.focus = -camera.z / camera.zoom; // for pixel precision. viewport = this.addChild(new Viewport3D) as Viewport3D; viewport.cacheAsBitmap = true; viewport.interactive = false; scene = new Scene3D(); // Pages setup pagesHolder = this.addChildAt(new BookPagesHolder, 0) as BookPagesHolder; pagesHolder.scaleX = pagesHolder.scaleY = 1; pageTweenParams = { base: Global.baseTween }; pageTweenParams.onUpdate = pageTweenUpdateHandler; // Jump to page timer jumpTimer = new Timer(Global.xmlFlipBook.jumpDelay * 1000); jumpTimer.addEventListener(TimerEvent.TIMER, jumpTimer_timerHandler, false, 0, true); // Auto flip timer. flipTimer = new Timer(Global.xmlAutoFlip.delay * 1000 / 360, 360); flipTimer.addEventListener(TimerEvent.TIMER, flipTimer_timerHandler, false, 0, true); flipTimer.addEventListener(TimerEvent.TIMER_COMPLETE, flipTimer_timerCompleteHandler, false, 0, true); } /** * Init book. * @param pPagesNode XML data. */ public function init(pPagesNode:XML):void { Global.overlay.state = FadeInOutItem.VISIBLE; pagesNode = pPagesNode; _estimPages = pagesNode.page.length(); trainLoadingTextInfo(); addAllPagesAsBlank(); loadNextPage(); //stage.addChild(new StatsView(renderer)); } /* Adds all the pages as blank pages */ private function addAllPagesAsBlank():void { var _crtPageIndex:int = 0; for each(var pageXML:XML in pagesNode.page) { initBookPage(new BookPage(null, pageXML, _crtPageIndex), String(pageXML.pageId.text())); Global.searchTags.addPageTags(_crtPageIndex, pageXML.searchTags[0].text()); _crtPageIndex++; } if (_crtPageIndex % 2 != 0) { initBookPage(new BookPage(null, pageXML, _crtPageIndex), StringUtil.uniqueStr()); } } /** * Train loading text size. * @param percentage Percentage. */ private function trainLoadingTextInfo(percentage:Number = 0):void { var str:String = Global.xmlTranslate.str15; str = str.replace(/%PAGE%/g, String(Math.ceil(_estimPages * 0.5) * 2)); str = str.replace(/%TOTAL_PAGES%/g, String(Math.ceil(_estimPages * 0.5) * 2)); Global.msgBox.sizeTrain(str.replace(/%PERCENTAGE%/g, String(Math.floor(1 * 100)))); } /** * Update loading text. * @param percentage */ private function updateLoadingTextInfo(percentage:Number = 0):void { if (pageLoadIndex < MIN_NUM_PAGES_LOAD - 1) { var str:String = Global.xmlTranslate.str15; str = str.replace(/%PAGE%/g, String(pageLoadIndex + 1)); str = str.replace(/%TOTAL_PAGES%/g, String(_estimPages)); Global.msgBox.label = str.replace(/%PERCENTAGE%/g, String(Math.floor(percentage * 100))); } } /** * Create Loader for a new page load. */ private function createNewPageLoader():void { removePageLoaderEvents(); pageLoader = new Loader; pageLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, pageLoader_ioErrorHandler, false, 0, true); pageLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, pageLoader_securityErrorHandler, false, 0, true); pageLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, pageLoader_progressHandler, false, 0, true); pageLoader.contentLoaderInfo.addEventListener(Event.INIT, pageLoader_initHandler, false, 0, true); } /** * Remove current page Loader events. */ private function removePageLoaderEvents():void { if (pageLoader) { pageLoader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, pageLoader_ioErrorHandler); pageLoader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, pageLoader_securityErrorHandler); pageLoader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS, pageLoader_progressHandler); pageLoader.contentLoaderInfo.removeEventListener(Event.INIT, pageLoader_initHandler); } } /** * Load next page if exists. */ private function loadNextPage():void { if (pageLoadIndex == MIN_NUM_PAGES_LOAD - 1) { addPage(_leftPageIndex + 1); pagesHolder.show(); fitPagesHolder(); Global.overlay.state = FadeInOutItem.INVISIBLE; Global.msgBox.state = FadeInOutItem.INVISIBLE; dispatchEvent(new Event(FLIP_READY)); } pageLoadIndex++; if (pageLoadIndex > 0 && currentContent) { (pagesArray[pageLoadIndex - 1] as BookPage).updatePageContent(currentContent); currentContent = null; } if (pageLoadIndex < pagesNode.page.length()) { updateLoadingTextInfo(); var pageSrc:String = pagesNode.page[pageLoadIndex].source[0].text(); if (!StringUtil.isBlank(pageSrc)) { createNewPageLoader(); pageLoader.load(new URLRequest(pageSrc)); } else { //initBookPage(new BookPage(null, pagesNode.page[pageLoadIndex], pageLoadIndex), String(pagesNode.page[pageLoadIndex].pageId.text())); loadNextPage(); } } else { /*if (_numPages % 2 != 0) { initBookPage(new BookPage(null, pagesNode.page[_numPages - 1], _numPages), StringUtil.uniqueStr()); }*/ removePageLoaderEvents(); /*Global.overlay.state = FadeInOutItem.INVISIBLE; Global.msgBox.state = FadeInOutItem.INVISIBLE; dispatchEvent(new Event(FLIP_READY));*/ } dispatchEvent(new Event(PAGE_VIEW_UPDATE)); } /** * Remove page. * @param pageIndex Index of the page to be removed. */ private function removePage(pageIndex:int):void { if (pageIndex >= 0 && pageIndex < _numPages) { pagesHolder.remPage(pagesArray[pageIndex]); } } /** * Add page. * @param pageIndex Index of the page to be added. */ private function addPage(pageIndex:int):void { if (pageIndex >= 0 && pageIndex < _numPages && Math.abs(_leftPageIndex - pageIndex) <= 3) { pagesHolder.addPage(pagesArray[pageIndex]); } } /** * Page load events handlers. */ private function pageLoader_ioErrorHandler(e:IOErrorEvent):void { trace("[ERROR] I/O error: " + e.text); //initBookPage(new BookPage(null, pagesNode.page[pageLoadIndex], pageLoadIndex), String(pagesNode.page[pageLoadIndex].pageId.text())); loadNextPage(); } private function pageLoader_securityErrorHandler(e:SecurityErrorEvent):void { trace("[ERROR] Security error: " + e.text); //initBookPage(new BookPage(null, pagesNode.page[pageLoadIndex], pageLoadIndex), String(pagesNode.page[pageLoadIndex].pageId.text())); loadNextPage(); } private function pageLoader_progressHandler(e:ProgressEvent):void { updateLoadingTextInfo(e.bytesLoaded / e.bytesTotal); } private function pageLoader_initHandler(e:Event):void { //initBookPage(new BookPage(pageLoader.content, pagesNode.page[pageLoadIndex], pageLoadIndex), String(pagesNode.page[pageLoadIndex].pageId.text())); currentContent = pageLoader.content; //Global.searchTags.addPageTags(pageLoadIndex, pagesNode.page[pageLoadIndex].searchTags[0].text()) loadNextPage(); } private function initBookPage(pageRef:BookPage, id:String):void { pageRef.addEventListener(MouseEvent.MOUSE_DOWN, page_mouseDownHandler, false, 0, true); pageRef.addEventListener(MouseEvent.ROLL_OVER, page_mouseRollOverHandler, false, 0, true); pageRef.addEventListener(MouseEvent.ROLL_OUT, page_mouseRollOutHandler, false, 0, true); pagesArray.push(pageRef); pageIds.push(id); _numPages++; } public function pageIdToIndex(pageId:String):int { return pageIds.indexOf(pageId); } public function pageIndexToId(pageIndex:int):String { return pageIds[pageIndex]; } /** * Page event handlers. */ private function page_mouseRollOverHandler(e:MouseEvent):void { autoFlipPaused = true; if (scene.numChildren == 0) pauseAutoFlip(); } private function page_mouseRollOutHandler(e:MouseEvent):void { autoFlipPaused = false; if (scene.numChildren == 0) resumeAutoFlip(); } private function page_mouseDownHandler(e:MouseEvent):void { var pageMc:BookPage = e.currentTarget as BookPage; if (pageMc.mouseInFlipZone) { mousePage3D = create3DPage(pageMc.side == BookPage.LEFT_SIDE); if (mousePage3D) { mousePageEnd = 0; if (mousePage3D.index == 0) mousePageEnd = 1; else if (mousePage3D.index == _numPages - 1) mousePageEnd = -1; stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler, false, 0, true); stage.addEventListener(MouseEvent.MOUSE_MOVE, stage_mouseMoveHandler, false, 0, true); pageTweenParams.time = 0.15; stage_mouseMoveHandler(null); mouseHasMoved = false; } pauseAutoFlip(); } } private function stage_mouseUpHandler(e:MouseEvent):void { stage.removeEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler); stage.removeEventListener(MouseEvent.MOUSE_MOVE, stage_mouseMoveHandler); pageTweenParams.time = Global.xmlBookPage.flipDuration; if (mousePage3D.side == BookPage.LEFT_SIDE) { if (mousePage3D.flipped || !mouseHasMoved) { if (Global.xmlAutoFlip.stopOnUserFlip && Global.optBar.btnAuto.isOn) { Global.optBar.btnAuto.isOn = false; Global.optBar.btnAuto_clickHandler(); } flipLeftToRight(mousePage3D); } else flipRightToLeft(mousePage3D); } else { if (mousePage3D.flipped || !mouseHasMoved) { if (Global.xmlAutoFlip.stopOnUserFlip && Global.optBar.btnAuto.isOn) { Global.optBar.btnAuto.isOn = false; Global.optBar.btnAuto_clickHandler(); } flipRightToLeft(mousePage3D); } else flipLeftToRight(mousePage3D); } mousePage3D = null; } private function stage_mouseMoveHandler(e:MouseEvent):void { mouseHasMoved = true; tweenPage(mousePage3D, pagesHolder.mouseX / Global.xmlBookPage.width, pagesHolder.mouseY / Global.xmlBookPage.height); } /** * Page flip animation. * @param page3DRef BookPage3D instance. * @param xPer Horizontal flip percentage. * @param yPer Vertical page twist percenetage. * @param dieAtEnd Remove 3D page at tween end. */ private function tweenPage(page3DRef:BookPage3D, xPer:Number, yPer:Number, dieAtEnd:Boolean = false):void { pageTweenParams.bendRotation = xPer; pageTweenParams.bendForce = xPer; pageTweenParams.bendAngle = yPer; pageTweenParams.onUpdateParams = [page3DRef]; if (dieAtEnd) { pageTweenParams.onComplete = pageTweenCompleteHandler; pageTweenParams.onCompleteParams = [page3DRef]; if (page3DRef.side == BookPage.LEFT_SIDE) { _leftPageIndex = xPer == 1 ? page3DRef.index - 2 : page3DRef.index; if (_leftPageIndex == -1) pagesHolder.position = -1; else if (_leftPageIndex < numPages - 1) pagesHolder.position = 0; } else { _leftPageIndex = xPer == -1 ? page3DRef.index + 1 : page3DRef.index - 1; if (_leftPageIndex == numPages - 1) pagesHolder.position = 1; else if (_leftPageIndex > -1) pagesHolder.position = 0; } dispatchEvent(new Event(PAGE_VIEW_UPDATE)); } else { delete pageTweenParams.onComplete; delete pageTweenParams.onCompleteParams; } Tweener.addTween(page3DRef, pageTweenParams); } /** * Render page. * @param page3DRef BookPage3D instance. */ private function pageTweenUpdateHandler(page3DRef:BookPage3D):void { page3DRef.applyBendParams(); render3DScene(page3DRef); } /** * On tween complete, remove 3D page. * @param page3DRef BookPage3D instance. */ private function pageTweenCompleteHandler(page3DRef:BookPage3D):void { if (page3DRef.side == BookPage.LEFT_SIDE) { if (page3DRef.flipped) { addPage(page3DRef.index - 1); removePage(page3DRef.index + 1); } else { addPage(page3DRef.index); removePage(page3DRef.index - 2); } } else { if (page3DRef.flipped) { addPage(page3DRef.index + 1); removePage(page3DRef.index - 1); } else { addPage(page3DRef.index); removePage(page3DRef.index + 2); } } page3DRef.die(); scene.removeChild(page3DRef); render3DScene(null); if (scene.numChildren == 0) { crtFlipDir = FLIP_NONE; resumeAutoFlip(); } /*for (var i:int = 0; i < pagesArray.length; i++) { var mc:BookPage = pagesArray[i]; if (mc.parent) { trace(mc.index); } } trace("===========")*/ } /** * Render 3D scene. */ public function render3DScene(page3DRef:BookPage3D, force:Boolean = true):void { renderer.renderScene(scene, camera, viewport); } /** * Create 3D page. * @param left If true, create at left. * @return Created page instance or null. */ public function create3DPage(left:Boolean = true):BookPage3D { var page3DMc:BookPage3D; if (crtFlipDir == FLIP_NONE || (left && crtFlipDir != FLIP_RTL) || (!left && crtFlipDir != FLIP_LTR)) { var idx:int = left ? _leftPageIndex : _leftPageIndex + 1; if (idx >= 0 && idx < _numPages) { var pageMc:BookPage = pagesArray[idx]; removePage(pageMc.index); if (pageMc.side == BookPage.RIGHT_SIDE) { page3DMc = new BookPage3DRight(pageMc.bitmap, pagesArray[pageMc.index + 1].bitmap, pageMc.index); addPage(pageMc.index + 2); } else { page3DMc = new BookPage3DLeft(pageMc.bitmap, pagesArray[pageMc.index - 1].bitmap, pageMc.index); addPage(pageMc.index - 2); } pageMc.page3D = page3DMc; page3DMc.x = pagesHolder.holderX * pagesHolder.scaleX; scene.addChild(page3DMc); render3DScene(page3DMc); crtFlipDir = left ? FLIP_LTR : FLIP_RTL; } } return page3DMc; } /** * Flip to next page. */ public function nextPage(ignoreTime:Boolean = false):void { if (!ignoreTime) pageTweenParams.time = Global.xmlBookPage.flipDuration; var page3DMc:BookPage3D = create3DPage(false); if (page3DMc) flipRightToLeft(page3DMc); } /** * Flip to previous page. */ public function prevPage(ignoreTime:Boolean = false):void { if (!ignoreTime) pageTweenParams.time = Global.xmlBookPage.flipDuration; var page3DMc:BookPage3D = create3DPage(); if (page3DMc) flipLeftToRight(page3DMc); } /** * Jump to first page. */ public function jumpToFirst():void { jumpTo(0); } /** * Jump to last page. */ public function jumpToLast():void { jumpTo(_numPages - 1); } /** * Jump to page index. * @param pageIndex Page index starting from 0. */ public function jumpTo(pageIndex:int):void { pageTweenParams.time = 0.3; flipsCount = 0; if (pageIndex >= 0 && pageIndex < _numPages) { pageIndex = Math.ceil(pageIndex * 0.5) * 2 - 1; var pageDif:int = _leftPageIndex - pageIndex; var pageDifAbs:int = Math.abs(pageDif); if (pageDifAbs <= 2) pageTweenParams.time = Global.xmlBookPage.flipDuration; if (pageDifAbs) { if (crtFlipDir != FLIP_NONE && crtFlipDir != NumberUtil.sign(pageDif)) pageDifAbs += 2; jumpTimer.reset(); jumpDir = pageDif < 0 ? JUMP_TO_NEXT : JUMP_TO_PREV; jumpTimer.repeatCount = pageDifAbs * 0.5; jumpTimer.start(); } } } /** * Jump timer page flip. */ private function jumpTimer_timerHandler(e:TimerEvent):void { if (flipsCount == 5) { roughPageIndexUpdate(_leftPageIndex + (jumpDir == JUMP_TO_NEXT ? 2 : -2) * (jumpTimer.repeatCount - jumpTimer.currentCount)); jumpTimer.reset(); } if (jumpDir == JUMP_TO_NEXT) nextPage(true); else prevPage(true); flipsCount++; } /** * Flip page from left to right. */ private function flipLeftToRight(page3DRef:BookPage3D):void { if (page3DRef.side == BookPage.LEFT_SIDE) resetAutoFlip(); tweenPage(page3DRef, 1, 1, true); } /** * Flip page from right to left. */ private function flipRightToLeft(page3DRef:BookPage3D):void { if (page3DRef.side == BookPage.RIGHT_SIDE) resetAutoFlip(); tweenPage(page3DRef, -1, 1, true); } /** * Autoflip. */ public function startAutoFlip():void { if (!autoFlip) { autoFlip = true; flipTimer.start(); } } public function stopAutoFlip():void { if (autoFlip) { autoFlip = false; flipTimer.reset(); } } private function pauseAutoFlip():void { if (autoFlip) flipTimer.stop(); } private function resumeAutoFlip():void { if (autoFlip && !autoFlipPaused) flipTimer.start(); } private function resetAutoFlip():void { if (autoFlip) { flipTimer.reset(); dispatchEvent(new ParamEvent(AUTO_FLIP_TICK, { count: 0 } )); } } private function flipTimer_timerHandler(e:TimerEvent):void { dispatchEvent(new ParamEvent(AUTO_FLIP_TICK, { count: flipTimer.currentCount } )); } private function flipTimer_timerCompleteHandler(e:TimerEvent):void { flipTimer.reset(); if (_leftPageIndex == _numPages - 1) jumpTo(0); else nextPage(); } /** * Print. */ public function printLeftPage():void { printPages(_leftPageIndex); } public function printRightPage():void { printPages(_leftPageIndex + 1); } public function printBothPages():void { printPages(_leftPageIndex, 2); } public function printAllPages():void { printPages(0, _numPages); } public function printPages(startIndex:int, pageCount:int = 1):void { if (startIndex >= 0 && startIndex < _numPages) { var endIndex:int = Math.min(_numPages, startIndex + pageCount) - 1; printJob = new PrintJob(); if (printJob.start()) { var pageToPrint:BookPage; for (var i:int = startIndex; i <= endIndex; ++i) { pageToPrint = pagesArray[i]; pageToPrint.printVersion(true, printJob.pageWidth, printJob.pageHeight); printJobOptions.printAsBitmap = pageToPrint.printAsBitmap; printJob.addPage(pageToPrint, null, printJobOptions); pageToPrint.printVersion(false); } printJob.send(); } printJob = null; } } /** * Update page index instantly. * @param pageIndex New page index. */ public function roughPageIndexUpdate(pageIndex:int):void { if (pageIndex >= 0 && pageIndex < _numPages) { pageIndex = Math.ceil(pageIndex * 0.5) * 2 - 1; removePage(leftPageIndex); removePage(leftPageIndex + 1); _leftPageIndex = pageIndex; addPage(pageIndex); addPage(pageIndex + 1); pagesHolder.roughPositionChange(leftPageIndex == -1 ? -1 : (leftPageIndex == numPages - 1 ? 1 : 0)); dispatchEvent(new Event(PAGE_VIEW_UPDATE)); } } /** * Properties. */ public function get numPages():int { return _numPages; } public function get estimPages():int { return _estimPages; } public function get leftPageIndex():int { return _leftPageIndex; } /* Fit pages holder */ public function fitPagesHolder():void { var o:Object = Resize.getParams(viewport.viewportWidth - 20, viewport.viewportHeight - 20, 2 * Global.xmlBookPage.width, Global.xmlBookPage.height, 0, 0, Resize.RESIZE_TO_FIT); pagesHolder.scaleX = pagesHolder.scaleY = o.h / Global.xmlBookPage.height; if (o.h != Global.xmlBookPage.height) { pagesHolder.width = Math.floor(pagesHolder.width * 0.5) * 2; pagesHolder.height = Math.floor(pagesHolder.height * 0.5) * 2; } } /** * Overrides. */ override public function get width():Number { return viewport.width; } override public function set width(value:Number):void { value = Math.floor(value * 0.5) * 2; if (Global.xmlBookPage.width % 2) value++; if (viewport.viewportWidth != value) { viewport.viewportWidth = value; w2 = Math.round(value * 0.5); pagesHolder.x = w2; render3DScene(null, false); fitPagesHolder(); } } override public function get height():Number { return viewport.height; } override public function set height(value:Number):void { value = Math.floor(value * 0.5) * 2; if (Global.xmlBookPage.height % 2) value++; if (viewport.viewportHeight != value) { viewport.viewportHeight = value; h2 = Math.round(value * 0.5); pagesHolder.y = h2; render3DScene(null, false); fitPagesHolder(); } } } }