diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 20caed5..336aa55 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,10 @@ CVS-* CVS/* *.iml -/.settings/* +.settings/* +.settings +.idea/* +.idea /html-template/* /bin-debug/* diff --git a/.gitmodules b/.gitmodules old mode 100644 new mode 100755 diff --git a/CHANGELOG.textile b/CHANGELOG.textile old mode 100644 new mode 100755 diff --git a/CONTRIBUTORS.textile b/CONTRIBUTORS.textile old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.textile b/README.textile old mode 100644 new mode 100755 diff --git a/add_to_build_later.txt b/add_to_build_later.txt new file mode 100755 index 0000000..409d47c --- /dev/null +++ b/add_to_build_later.txt @@ -0,0 +1,33 @@ + + + + [test] Running Unit Tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + [test] Finished running Unit Tests + diff --git a/build-old.xml b/build-old.xml new file mode 100755 index 0000000..4eb1f83 --- /dev/null +++ b/build-old.xml @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [clean] Removing Build and Report directories + + + + + + + + + [clean] Build and Report directories removed + + + + [init] Creating Bin and Report directories + + + [init] Bin and Report directories created + + + + + [build-core] Compiling core release SWC + [build-core] Using Flex SDK at: ${FLEX_HOME} + + + + + + + + + + + + + + + + + + + + + [build-core] Compiling core release SWC + [build-core] Using Flex SDK at: ${FLEX_HOME} + + + + + + + + + + + + + + + + + + + + + + [build-gestures] Compiling gestures release SWC + [build-gestures] Using Flex SDK at: ${FLEX_HOME} + + + + + + + + + + + + + + + + + + + + + + [build-extensions] Compiling extensions release SWC + [build-extensions] Using Flex SDK at: ${FLEX_HOME} + + + + + + + + + + + + + + + + + + + + + + [build-components] Compiling components release SWC + [build-components] Using Flex SDK at: ${FLEX_HOME} + + + + + + + + + + + + + + + + + + + + + + [asdoc] Generating ASDOC documentation + + + + + + + + + + + + + + + + + + + [asdoc] ASDOC documentation generated successfully + + + + + + + + [package] Packaging Release + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [package] Release packaged successfully + + \ No newline at end of file diff --git a/build.properties b/build.properties old mode 100644 new mode 100755 index 0b318c3..9028af2 --- a/build.properties +++ b/build.properties @@ -2,17 +2,29 @@ project.name=tinytlf #Version number for current tinytlf releases -tinytlf.ver.num=v0.1.0 +tinytlf.ver.num=v1.0 project.name.versioned=${project.name}-${tinytlf.ver.num} #build locations asdoc.loc=${FLEX_HOME}/bin/asdoc -main.src.loc=${basedir}/src + +utils.src.loc=${basedir}/tinytlf-fte-utils/src +core.src.loc=${basedir}/tinytlf-core/src +gestures.src.loc=${basedir}/tinytlf-gestures/src +edit.src.loc=${basedir}/tinytlf-edit/src +decorations.src.loc=${basedir}/tinytlf-decorations/src +layouts.src.loc=${basedir}/tinytlf-layouts/src +html.src.loc=${basedir}/tinytlf-xhtml/src +components.src.loc=${basedir}/tinytlf-components/src + test.src.loc=${basedir}/test + doc.loc=${basedir}/doc lib.loc=${basedir}/build/libs bin.loc=${basedir}/bin report.loc=${basedir}/report +report.xml.loc=${basedir}/report/xml +report.html.loc=${basedir}/report/html dist.loc=${basedir}/dist template.loc=${basedir}/build/templates diff --git a/build.xml b/build.xml old mode 100644 new mode 100755 index 5890a41..9782221 --- a/build.xml +++ b/build.xml @@ -1,160 +1,171 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [clean] Removing Build and Report directories - - - - - - - - - [clean] Build and Report directories removed - - - - [init] Creating Bin and Report directories - - - [init] Bin and Report directories created - - - - [compile] Compiling release SWC - [compile] Using Flex SDK at: ${FLEX_HOME} - - - - - - - - - - - - - - [compile] Release SWC ${project.name.versioned}.swc created successfully - - - - [test] Running Unit Tests - - - - - - - - - - - - - - - - - - - - - - - - - [test] Finished running Unit Tests - - - - [asdoc] Generating ASDOC documentation - - - - - - - - - - - - - [asdoc] ASDOC documentation generated successfully - - - - - - - - [package] Packaging Release - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [package] Release packaged successfully - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [clean] Removing Bin and Report directories + + + + + + + + + + + + + + + [clean] Bin and Report directories removed + + + + + [init] Creating Bin and Report directories + + + + + [init] Bin and Report directories created + + + + + [test] Running Unit Tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [test] Finished running Unit Tests + + + + + [asdoc] Generating ASDOC documentation + + + + + + + + + + + + + + + + + + [asdoc] ASDOC documentation generated successfully + + + + + [compile] Compiling release SWC + [compile] Using Flex SDK at: ${FLEX_HOME} + + + + + + + + + + + + + + + + + + + + + + + + + + + + [compile] Release SWC @{project}.swc created successfully + + + + + diff --git a/build/assets/tinytlf.png b/build/assets/tinytlf.png old mode 100644 new mode 100755 diff --git a/build/libs/FCSS-v1.1.0.swc b/build/libs/FCSS-v1.1.0.swc old mode 100644 new mode 100755 diff --git a/build/libs/FlexUnit4.swc b/build/libs/FlexUnit4.swc old mode 100644 new mode 100755 diff --git a/build/libs/FlexUnit4CIListener.swc b/build/libs/FlexUnit4CIListener.swc old mode 100644 new mode 100755 diff --git a/build/libs/MinimalComps_0_9_7.swc b/build/libs/MinimalComps_0_9_7.swc new file mode 100644 index 0000000..20718bc Binary files /dev/null and b/build/libs/MinimalComps_0_9_7.swc differ diff --git a/build/libs/as3-gestures.swc b/build/libs/as3-gestures.swc new file mode 100644 index 0000000..d500999 Binary files /dev/null and b/build/libs/as3-gestures.swc differ diff --git a/build/libs/flexUnit4UIRunner.swc b/build/libs/flexUnit4UIRunner.swc old mode 100644 new mode 100755 diff --git a/build/libs/flexUnitTasks.jar b/build/libs/flexUnitTasks.jar old mode 100644 new mode 100755 diff --git a/build/libs/hamcrest-as3-1.1.0.swc b/build/libs/hamcrest-as3-1.1.0.swc old mode 100644 new mode 100755 diff --git a/build/libs/link-report.xsl b/build/libs/link-report.xsl new file mode 100644 index 0000000..2c79783 --- /dev/null +++ b/build/libs/link-report.xsl @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + Project dependency analysis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
    + +
+
    + +
+
+ + +
+ + + KB +
+ + +
+ + + + + + + + + + + external + + + + + + + + + + + + + dependency + + + prequisite + + + + +
  • +
    + + + + + + +
    \ No newline at end of file diff --git a/build/libs/mockolate-0.8.0.swc b/build/libs/mockolate-0.8.0.swc old mode 100644 new mode 100755 diff --git a/build/templates/README.tmpl b/build/templates/README.tmpl old mode 100644 new mode 100755 diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..59275f5 --- /dev/null +++ b/config.xml @@ -0,0 +1,33 @@ + + + true + + en_US + + true + false + true + true + + ${flexlib}/libs/player/10.2/playerglobal.swc + ${flexlib}/libs/framework.swc + ${flexlib}/libs/rpc.swc + + true + + + Event + + + + + + + 10.2.0 + + + Paul Taylor + TinyTLF! + TinyTLF! + + \ No newline at end of file diff --git a/formatting.properties b/formatting.properties deleted file mode 100644 index 3745828..0000000 --- a/formatting.properties +++ /dev/null @@ -1,100 +0,0 @@ -#FlexPrettyPrintSettings -#Sun Aug 30 00:38:09 SAST 2009 -ASRearr_UseModifierOrder_Function=true -Actionscript.useBraceStyle=false -ASRearr_ModifierOrder_Property=,override,public,private,protected,internal,static,dynamic,final -ASRearr_ModifierOrder_Class=,override,public,private,protected,internal,static,dynamic,final -ASRearr_ElementSortStaticProperties=true -ASRearr_ElementStaticFunctionVisibilityOrder=public\:true,protected\:true,internal\:true,private\:true, -MXML.attrWrapMode=52 -ASRearr_UseElementStaticPropertyVisibilityOrder=true -ASRearr_ElementSortImports=true -ASRearr_ElementOrder=Import,Include,Namespace Definition,Default Namespace,Namespace Use,Static Property,Static Function,Constructor,Property,Function, -Actionscript.spacesAfterComma=1 -Actionscript.advancedSpacesAroundEqualsInOptionalParameters=0 -ASRearr_ElementFunctionVisibilityOrder=public\:true,protected\:true,internal\:true,private\:true, -MXML.onlyFormatASIfCDATABlock=false -Actionscript.spacesBeforeComma=0 -Actionscript.advancedSpacesInsideLiteralBraces=1 -Actionscript.wrapArrayDeclMode=4 -Flex.useTabs=true -ASRearr_ElementSortMetatags=false -MXML.attrsToKeepOnSameLine=4 -MXML.sortAttrMode=0 -Actionscript.wrapMethodCallMode=4 -Actionscript.spacesBeforeControlOpenParen=1 -Actionscript.spacesAfterColons=0 -Actionscript.blankLinesBeforeFunctions=1 -MXML.spacesAroundEquals=0 -ASRearr_UseModifierOrder_Class=true -Actionscript.breakLinesBeforeComma=false -Actionscript.spacesAroundAssignment=1 -ASRearr_ElementSortIncludes=true -MXML.wrapIndentStyle=1001 -Actionscript.leaveExtraWhitespaceAroundVarDecls=false -MXML.maxLineLength=200 -Actionscript.putEmptyStatementsOnNewLine=true -Actionscript.wrapExpressionMode=4 -ASRearr_UseElementStaticFunctionVisibilityOrder=true -Actionscript.spacesAroundColons=0 -Actionscript.advancedUseSpacesAroundEqualsInOptionalParameters=false -Actionscript.putOpenBraceOnNewLine=true -Actionscript.blankLinesBeforeControlStatements=0 -MXML.blankLinesBeforeTags=0 -Actionscript.dontIndentPackageItems=false -Actionscript.keepBlankLines=true -MXML.keepBlankLines=true -ASRearr_UseMetatagOrder=false -MXML.attrGroups=name\=properties|sort\=11|wrap\=54|attrs\=allowDisjointSelection,allowMultipleSelection,allowThumbOverlap,allowTrackClick,autoLayout,autoRepeat,automationName,cachePolicy,class,clipContent,condenseWhite,conversion,creationIndex,creationPolicy,currentState,data,dataDescriptor,dataProvider,dataTipFormatFunction,dayNames,defaultButton,direction,disabledDays,disabledRanges,displayedMonth,displayedYear,doubleClickEnabled,emphasized,enabled,explicitHeight,explicitMaxHeight,explicitMaxWidth,explicitMinHeight,explicitMinWidth,explicitWidth,firstDayOfWeek,focusEnabled,fontContext,height,horizontalLineScrollSize,horizontalPageScrollSize,horizontalScrollBar,horizontalScrollPolicy,horizontalScrollPosition,htmlText,icon,iconField,id,imeMode,includeInLayout,indeterminate,label,labelField,labelFunction,labelPlacement,labels,layout,lineScrollSize,listData,liveDragging,maxChars,maxHeight,maxScrollPosition,maxWidth,maxYear,maximum,measuredHeight,measuredMinHeight,measuredMinWidth,measuredWidth,menuBarItemRenderer,menuBarItems,menus,minHeight,minScrollPosition,minWidth,minYear,minimum,mode,monthNames,monthSymbol,mouseFocusEnabled,pageScrollSize,pageSize,percentHeight,percentWidth,scaleX,scaleY,scrollPosition,selectable,selectableRange,selected,selectedDate,selectedField,selectedIndex,selectedRanges,showDataTip,showRoot,showToday,sliderDataTipClass,sliderThumbClass,snapInterval,source,states,stepSize,stickyHighlighting,styleName,text,text,thumbCount,tickInterval,tickValues,toggle,toolTip,transitions,truncateToFit,validationSubField,value,value,verticalLineScrollSize,verticalPageScrollSize,verticalScrollBar,verticalScrollPolicy,verticalScrollPosition,width,x,y,yearNavigationEnabled,yearSymbol,|\nname\=events|sort\=11|wrap\=54|attrs\=add,added,activate,addedToStage,buttonDown,change,childAdd,childIndexChange,childRemove,clickHandler,clear,click,complete,contextMenu,copy,creationComplete,currentStateChange,currentStateChanging,cut,dataChange,deactivate,doubleClick,dragComplete,dragDrop,dragEnter,dragExit,dragOver,dragStart,effectEnd,effectStart,enterFrame,enterState,exitFrame,exitState,focusIn,focusOut,frameConstructed,hide,httpStatus,init,initialize,invalid,ioError,itemClick,itemRollOut,itemRollOver,keyDown,keyFocusChange,keyUp,menuHide,menuShow,middleClick,middleMouseDown,middleMouseUp,mouseDown,mouseUp,mouseOver,mouseMove,mouseOut,mouseFocusChange,mouseWheel,mouseDownOutside,mouseWheelOutside,move,nativeDragComplete,nativeDragDrop,nativeDragEnter,nativeDragExit,nativeDragOver,nativeDragStart,nativeDragUpdate,open,paste,preinitialize,progress,record,remove,removed,removedFromStage,render,resize,rightClick,rightMouseDown,rightMouseUp,rollOut,rollOver,scroll,securityError,selectAll,show,tabChildrenChange,tabEnabledChange,tabIndexChange,thumbDrag,thumbPress,thumbRelease,toolTipCreate,toolTipEnd,toolTipHide,toolTipShow,toolTipShown,toolTipStart,updateComplete,unload,valid,valueCommit,|\nname\=styles|sort\=11|wrap\=54|attrs\=backgroundAlpha,backgroundAttachment,backgroundColor,backgroundDisabledColor,backgroundImage,backgroundSize,backgroundSkin,barColor,barSkin,borderColor,borderSides,borderSkin,borderStyle,borderThickness,bottom,color,cornerRadius,dataTipOffset,dataTipPrecision,dataTipStyleName,disabledColor,disabledIcon,disabledIconColor,disabledSkin,disbledOverlayAlpha,downArrowDisabledSkin,downArrowDownSkin,downArrowOverSkin,downArrowUpSkin,downIcon,downSkin,dropShadowColor,dropShadowEnabled,errorColor,fillAlphas,fillColors,focusAlpha,focusBlendMode,focusRoundedCorners,focusSkin,focusThickness,fontAntiAliasType,fontFamily,fontGridFitType,fontSharpness,fontSize,fontStyle,fontThickness,fontWeight,fontfamily,headerColors,headerStyleName,highlightAlphas,horizontalAlign,horizontalCenter,horizontalGap,horizontalScrollBarStyleName,icon,iconColor,indeterminateMoveInterval,indeterminateSkin,itemDownSkin,itemOverSkin,itemUpSkin,kerning,labelOffset,labelStyleName,labelWidth,leading,left,letterSpacing,maskSkin,menuStyleName,nextMonthDisabledSkin,nextMonthDownSkin,nextMonthOverSkin,nextMonthSkin,nextMonthUpSkin,nextYearDisabledSkin,nextYearDownSkin,nextYearOverSkin,nextYearSkin,nextYearUpSkin,overIcon,overSkin,paddingBottom,paddingLeft,paddingRight,paddingTop,prevMonthDisabledSkin,prevMonthDownSkin,prevMonthOverSkin,prevMonthSkin,prevMonthUpSkin,prevYearDisabledSkin,prevYearDownSkin,prevYearOverSkin,prevYearSkin,prevYearUpSkin,repeatDelay,repeatInterval,right,rollOverColor,rollOverIndicatorSkin,selectedDisabledIcon,selectedDisabledSkin,selectedDownIcon,selectedDownSkin,selectedOverIcon,selectedOverSkin,selectedUpIcon,selectedUpSkin,selectionColor,selectionIndicatorSkin,shadowColor,shadowDirection,shadowDistance,showTrackHighlight,skin,slideDuration,slideEasingFunction,strokeColor,strokeWidth,textAlign,textDecoration,textIndent,textRollOverColor,textSelectedColor,themeColor,thumbDisabledSkin,thumbDownSkin,thumbIcon,thumbOffset,thumbOverSkin,thumbUpSkin,tickColor,tickLength,tickOffset,tickThickness,todayColor,todayIndicatorSkin,todayStyleName,top,tracHighlightSkin,trackColors,trackHeight,trackMargin,trackSkin,upArrowDisabledSkin,upArrowDownSkin,upArrowOverSkin,upArrowUpSkin,upIcon,upSkin,verticalAlign,verticalCenter,verticalGap,verticalScrollBarStyleName,weekDayStyleName,|\nname\=effects|sort\=11|wrap\=54|attrs\=addedEffect,completeEffect,creationCompleteEffect,focusInEffect,focusOutEffect,hideEffect,mouseDownEffect,mouseUpEffect,moveEffect,removedEffect,resizeEffect,rollOutEffect,rollOverEffect,showEffect,|\n -ASRearr_UseModifierOrder_Property=true -ASRearr_MoveImportsOutsideClass=true -Actionscript.maxLineLength=200 -Actionscript.spacesInsideParens=0 -ASRearr_ElementSortProperties=true -Actionscript.advancedSpacesInsideArrayRefBrackets=0 -Actionscript.newLineAfterBindable=true -Actionscript.blankLinesToKeep=0 -Actionscript.keepElseIfOnSameLine=true -Actionscript.blankLinesBeforeProperties=0 -Actionscript.spacesBeforeColons=0 -Actionscript.alwaysGenerateIndent=true -ASRearr_UseElementPropertyVisibilityOrder=true -Actionscript.spacesAfterLabel=1 -Actionscript.wrapMethodDeclMode=1 -Actionscript.useGlobalSpacesAroundColons=true -ASRearr_ModifierOrder_Function=,override,public,private,protected,internal,static,dynamic,final -ASRearr_ElementStaticPropertyVisibilityOrder=public\:true,protected\:true,internal\:true,private\:true, -MXML.sortExtraAttrs=false -Actionscript.tabCountForHangingIndent=1 -ASRearr_ElementSortFunctions=true -ASRearr_ElementPropertyVisibilityOrder=public\:true,protected\:true,internal\:true,private\:true, -Actionscript.wrapEmbeddedXMLMode=2 -ASRearr_UseElementOrder=true -ASRearr_UseGlobalModifierOrder=true -Actionscript.useGlobalSpacesInsideParens=true -Actionscript.putCatchOnNewLine=true -ASRearr_ElementSortNamespaces=true -Actionscript.putElseOnNewLine=true -MXML.spacesBeforeEmptyTagEnd=0 -ASRearr_ElementSortStaticFunctions=true -Actionscript.wrapIndentStyle=1000 -ASRearr_MetatagOrder=ArrayElementType,Bindable,DefaultProperty,Deprecated,Effect,Embed,Event,Exclude,ExcludeClass,IconFile,Inspectable,InstanceType,NonCommittingChangeEvent,RemoteClass,Style,SWF,Transient, -Actionscript.braceStyle=5 -ASRearr_UseElementFunctionVisibilityOrder=true -Actionscript.spacesAroundBinarySymbolicOperator=1 -Actionscript.keepSLCommentsOnColumn1=true -MXML.tagsCanFormat=mx\:List,fx\:List, -MXML.sortAttrData= -Actionscript.blankLinesBeforeClasses=1 -MXML.tagsToHaveBlankLinesAddedBeforeThem= -MXML.useAttrsToKeepOnSameLine=false -ASRearr_UseImportOrder=true -MXML.blankLinesBetweenSiblingTags=0 -Actionscript.collapseSpacesForAdjacentParens=true -Actionscript.advancedSpacesInsideArrayDeclBrackets=1 -Actionscript.advancedSpacesInsideParens=0 -MXML.tagsCannotFormat=mx\:String,fx\:String, -MXML.attrsPerLine=1 -ASRearr_ImportOrder=adobe,com,flash,mx, -MXML.addNewlineAfterLastAttr=false -MXML.tagsWithASContent=.*\:add,.*\:added,.*\:activate,.*\:addedToStage,.*\:buttonDown,.*\:change,.*\:childAdd,.*\:childIndexChange,.*\:childRemove,.*\:clickHandler,.*\:clear,.*\:click,.*\:complete,.*\:contextMenu,.*\:copy,.*\:creationComplete,.*\:currentStateChange,.*\:currentStateChanging,.*\:cut,.*\:dataChange,.*\:deactivate,.*\:doubleClick,.*\:dragComplete,.*\:dragDrop,.*\:dragEnter,.*\:dragExit,.*\:dragOver,.*\:dragStart,.*\:effectEnd,.*\:effectStart,.*\:enterFrame,.*\:enterState,.*\:exitFrame,.*\:exitState,.*\:focusIn,.*\:focusOut,.*\:frameConstructed,.*\:hide,.*\:httpStatus,.*\:init,.*\:initialize,.*\:invalid,.*\:ioError,.*\:itemClick,.*\:itemRollOut,.*\:itemRollOver,.*\:keyDown,.*\:keyFocusChange,.*\:keyUp,.*\:menuHide,.*\:menuShow,.*\:middleClick,.*\:middleMouseDown,.*\:middleMouseUp,.*\:mouseDown,.*\:mouseUp,.*\:mouseOver,.*\:mouseMove,.*\:mouseOut,.*\:mouseFocusChange,.*\:mouseWheel,.*\:mouseDownOutside,.*\:mouseWheelOutside,.*\:move,.*\:nativeDragComplete,.*\:nativeDragDrop,.*\:nativeDragEnter,.*\:nativeDragExit,.*\:nativeDragOver,.*\:nativeDragStart,.*\:nativeDragUpdate,.*\:open,.*\:paste,.*\:preinitialize,.*\:progress,.*\:record,.*\:remove,.*\:removed,.*\:removedFromStage,.*\:render,.*\:resize,.*\:rightClick,.*\:rightMouseDown,.*\:rightMouseUp,.*\:rollOut,.*\:rollOver,.*\:scroll,.*\:securityError,.*\:selectAll,.*\:show,.*\:tabChildrenChange,.*\:tabEnabledChange,.*\:tabIndexChange,.*\:thumbDrag,.*\:thumbPress,.*\:thumbRelease,.*\:toolTipCreate,.*\:toolTipEnd,.*\:toolTipHide,.*\:toolTipShow,.*\:toolTipShown,.*\:toolTipStart,.*\:updateComplete,.*\:unload,.*\:valid,.*\:valueCommit,.*\:Script, diff --git a/src/org/tinytlf/ITextEngine.as b/src/org/tinytlf/ITextEngine.as deleted file mode 100644 index 9012695..0000000 --- a/src/org/tinytlf/ITextEngine.as +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf -{ - import flash.display.Stage; - - import org.tinytlf.decor.ITextDecor; - import org.tinytlf.interaction.ITextInteractor; - import org.tinytlf.layout.ITextLayout; - import org.tinytlf.layout.factory.ILayoutModelFactory; - import org.tinytlf.styles.ITextStyler; - - public interface ITextEngine - { - function get blockFactory():ILayoutModelFactory; - function set blockFactory(value:ILayoutModelFactory):void; - - function get decor():ITextDecor; - function set decor(textDecor:ITextDecor):void; - - function get interactor():ITextInteractor; - function set interactor(textInteractor:ITextInteractor):void; - - function get layout():ITextLayout; - function set layout(textlayout:ITextLayout):void; - - function set stage(theStage:Stage):void; - - function get styler():ITextStyler; - function set styler(textStyler:ITextStyler):void; - - function prerender(...args):void; - - function invalidate():void; - function invalidateLines():void; - function invalidateDecorations():void; - - function render():void; - function renderLines():void; - function renderDecorations():void; - } -} - diff --git a/src/org/tinytlf/TextEngine.as b/src/org/tinytlf/TextEngine.as deleted file mode 100644 index 286dee2..0000000 --- a/src/org/tinytlf/TextEngine.as +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf -{ - import flash.display.Stage; - import flash.events.Event; - import flash.events.EventDispatcher; - import flash.text.engine.TextBlock; - import flash.utils.setTimeout; - - import org.tinytlf.decor.*; - import org.tinytlf.extensions.xml.layout.factory.XMLBlockFactory; - import org.tinytlf.interaction.*; - import org.tinytlf.layout.*; - import org.tinytlf.layout.factory.*; - import org.tinytlf.styles.*; - - public class TextEngine extends EventDispatcher implements ITextEngine - { - public function TextEngine(stage:Stage = null) - { - this.stage = stage; - } - - protected var _blockFactory:ILayoutModelFactory; - - public function get blockFactory():ILayoutModelFactory - { - if(!_blockFactory) - _blockFactory = new XMLBlockFactory(); - - _blockFactory.engine = this; - - return _blockFactory; - } - - public function set blockFactory(value:ILayoutModelFactory):void - { - if(value === _blockFactory) - return; - - _blockFactory = value; - - blockFactory.engine = this; - } - - protected var _decor:ITextDecor; - - public function get decor():ITextDecor - { - if(!_decor) - _decor = new TextDecor(); - - _decor.engine = this; - - return _decor; - } - - public function set decor(textDecor:ITextDecor):void - { - if(textDecor == _decor) - return; - - _decor = textDecor; - - _decor.engine = this; - } - - protected var _interactor:ITextInteractor; - - public function get interactor():ITextInteractor - { - if(!_interactor) - _interactor = new TextInteractorBase(); - - _interactor.engine = this; - - return _interactor; - } - - public function set interactor(textInteractor:ITextInteractor):void - { - if(textInteractor == _interactor) - return; - - _interactor = textInteractor; - - _interactor.engine = this; - } - - protected var _layout:ITextLayout; - - public function get layout():ITextLayout - { - if(!_layout) - _layout = new TextLayoutBase(); - - _layout.engine = this; - - return _layout; - } - - public function set layout(textLayout:ITextLayout):void - { - if(textLayout == _layout) - return; - - _layout = textLayout; - - _layout.engine = this; - } - - protected var _stage:Stage; - - public function set stage(theStage:Stage):void - { - if(theStage === _stage) - return; - - _stage = theStage; - invalidateStage(); - } - - protected var _styler:ITextStyler; - - public function get styler():ITextStyler - { - if(!_styler) - _styler = new TextStyler(); - - _styler.engine = this; - - return _styler; - } - - public function set styler(textStyler:ITextStyler):void - { - if(textStyler == _styler) - return; - - _styler = textStyler; - } - - protected var blocks:Vector.; - - public function prerender(... args):void - { - decor.removeAll(); - blocks = blockFactory.createBlocks(args); - } - - public function invalidate():void - { - invalidateLines(); - invalidateDecorations(); - } - - protected var invalidateLinesFlag:Boolean = false; - - public function invalidateLines():void - { - if(invalidateLinesFlag) - return; - - invalidateLinesFlag = true; - invalidateStage(); - } - - protected var invalidateDecorationsFlag:Boolean = false; - - public function invalidateDecorations():void - { - if(invalidateDecorationsFlag) - return; - - invalidateDecorationsFlag = true; - invalidateStage(); - } - - protected function invalidateStage():void - { - if(!_stage) - return; - - _stage.addEventListener(Event.RENDER, onRender); - - if(rendering) - setTimeout(_stage.invalidate, 0); - else - _stage.invalidate(); - } - - protected function onRender(event:Event):void - { - if(!_stage) - return; - - _stage.removeEventListener(Event.RENDER, onRender); - - render(); - } - - protected var rendering:Boolean = false; - - public function render():void - { - rendering = true; - - if(invalidateLinesFlag) - renderLines(); - invalidateLinesFlag = false; - - if(invalidateDecorationsFlag) - renderDecorations(); - invalidateDecorationsFlag = false; - - rendering = false; - } - - public function renderLines():void - { - layout.clear(); - layout.render(blockFactory.blocks); - } - - public function renderDecorations():void - { - layout.resetShapes(); - decor.render(); - } - } -} - diff --git a/src/org/tinytlf/TextField.as b/src/org/tinytlf/TextField.as deleted file mode 100644 index f0b0306..0000000 --- a/src/org/tinytlf/TextField.as +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf -{ - import flash.display.DisplayObject; - import flash.display.Sprite; - import flash.events.Event; - import flash.events.EventPhase; - import flash.text.engine.TextLine; - - import org.tinytlf.core.IStyleAware; - import org.tinytlf.decor.decorations.BackgroundColorDecoration; - import org.tinytlf.decor.decorations.StrikeThroughDecoration; - import org.tinytlf.decor.decorations.UnderlineDecoration; - import org.tinytlf.extensions.xhtml.interaction.AnchorInteractor; - import org.tinytlf.extensions.xhtml.layout.adapter.AnchorAdapter; - import org.tinytlf.layout.ITextContainer; - import org.tinytlf.layout.TextContainerBase; - - public class TextField extends Sprite implements IStyleAware - { - public function TextField() - { - super(); - - width = 100; - - hookEngine(); - } - - private var _height:Number = 0; - override public function get height():Number - { - return _height; - } - - override public function set height(value:Number):void - { - if(!changed(height, value)) - return; - - _height = value; - container.allowedHeight = value; - engine.invalidate(); - } - - private var _width:Number = 0; - override public function get width():Number - { - return _width; - } - - override public function set width(value:Number):void - { - if(!changed(width, value)) - return; - - _width = value; - container.allowedWidth = value; - engine.invalidate(); - } - - override public function addChild(child:DisplayObject):DisplayObject - { - return super.addChild(child is TextLine ? hookLine(child) : child); - } - - private var _container:ITextContainer; - - public function get container():ITextContainer - { - if(!_container) - _container = new TextContainerBase(this); - - return _container; - } - - public function set container(textContainer:ITextContainer):void - { - if(textContainer == _container) - return; - - _container = textContainer; - _container.target = this; - } - - private var _engine:ITextEngine; - - public function get engine():ITextEngine - { - if(!_engine) - { - _engine = new TextEngine(stage); - _engine.layout.addContainer(container); - - if(!stage) - addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); - } - - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine == _engine) - return; - - _engine = textEngine; - } - - private var _text:String = ""; - public function set text(value:String):void - { - if(!changed(_text, value)) - return; - - _text = value; - engine.blockFactory.data = _text; - engine.prerender(); - engine.invalidate(); - } - - public function get style():Object - { - return engine.styler.style; - } - - public function set style(value:Object):void - { - engine.styler.style = value; - } - - public function clearStyle(styleProp:String):Boolean - { - return engine.styler.clearStyle(styleProp); - } - - public function getStyle(styleProp:String):* - { - return engine.styler.getStyle(styleProp); - } - - public function setStyle(styleProp:String, newValue:*):void - { - engine.styler.setStyle(styleProp, newValue); - } - - /** - * @private - * Called just before a line is added to the display list. - */ - protected function hookLine(line:DisplayObject):DisplayObject - { - return line; - } - - /** - * @private - * Called from the constructor, maps in all the properties that the engine - * uses. - */ - protected function hookEngine():void - { - //Default mapped text decorations. - engine.decor.mapDecoration("backgroundColor", BackgroundColorDecoration); - engine.decor.mapDecoration("underline", UnderlineDecoration); - engine.decor.mapDecoration("strikethrough", StrikeThroughDecoration); - - engine.blockFactory.mapElementAdapter("a", AnchorAdapter); - - engine.interactor.mapMirror("a", new AnchorInteractor()); - engine.styler.mapStyle("a", {underline:true}); - } - - private function onAddedToStage(event:Event):void - { - if(event.eventPhase != EventPhase.AT_TARGET) - return; - - removeEventListener(event.type, onAddedToStage); - engine.stage = stage; - } - - private function changed(oldVal:*, newVal:*):Boolean - { - return Boolean(newVal !== oldVal); - } - } -} - diff --git a/src/org/tinytlf/core/IStyleAware.as b/src/org/tinytlf/core/IStyleAware.as deleted file mode 100644 index 3786fdc..0000000 --- a/src/org/tinytlf/core/IStyleAware.as +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.core -{ - public interface IStyleAware - { - function get style():Object; - function set style(value:Object):void; - - function clearStyle(styleProp:String):Boolean; - function getStyle(styleProp:String):*; - function setStyle(styleProp:String, newValue:*):void; - } -} - diff --git a/src/org/tinytlf/core/StyleAwareActor.as b/src/org/tinytlf/core/StyleAwareActor.as deleted file mode 100644 index 57ba775..0000000 --- a/src/org/tinytlf/core/StyleAwareActor.as +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.core -{ - import flash.utils.Proxy; - import flash.utils.describeType; - import flash.utils.flash_proxy; - - use namespace flash_proxy; - - /** - * StyleAwareActor is a useful base class for objects with sealed properties - * but who also wish to dynamically accept and store named values. - * - * Since he is a Proxy implementation, he overrides the flash_proxy functions - * for setting and retrieving data. If you are calling a sealed property on - * StyleAwareActor or one of his subclasses, the property or function is called - * like normal. However, if you dynamically call or set a property on him, he - * calls his getStyle and setStyle methods instead. - * - * StyleAwareActor has an internal styles object on which these - * properties and values are stored. However, you can override this - * functionality by passing in your own implementation to store styles on. You - * can do this by calling setStyle("styleProxy", myProxyImplem). - * This will set the myProxyImpl instance as the new internal - * styles storage object, as well as copy over all the key/value pairs currently - * on the myProxyImpl instance. - * - * This is particularly useful if you wish to proxy together multiple - * StyleAwareActors for something similar to CSS inheritance, or to support - * external CSS implementations (currently Flex and F*CSS). - */ - public class StyleAwareActor extends Proxy implements IStyleAware - { - public function StyleAwareActor(styleObject:Object = null) - { - if(!styleObject) - return; - - style = styleObject; - } - - public function toString():String - { - return style.toString(); - } - - private var _style:Object; - - public function get style():Object - { - return _style; - } - - public function set style(value:Object):void - { - if(value === _style) - return; - - if(!(value is String)) - { - var proxy:Object; - var styleProp:String; - - // Use value as the new styles object. This allows you to pass in - // and use your own subclass of StyleAwareActor - // (useful for F*CSS or Flex styles) - if(value is IStyleAware) - { - proxy = styles; - styles = value; - - // Copy values from the proxy styles Object to this. - // Since here we're copying the old styles onto the replacement styles - // Object, we have to be sure not to replace any styles that already - // exist on the new guy. - for(styleProp in proxy) - if(!(styleProp in this)) - this[styleProp] = proxy[styleProp]; - } - else - { - proxy = value; - // Copy values from the proxy styles Object to this. - for(styleProp in proxy) - this[styleProp] = proxy[styleProp]; - } - } - - _style = value; - } - - protected var styles:Object = {}; - - public function clearStyle(styleProp:String):Boolean - { - return styleProp in styles ? delete styles[styleProp] : false; - } - - public function getStyle(styleProp:String):* - { - return styleProp in styles ? styles[styleProp] : null; - } - - public function setStyle(styleProp:String, newValue:*):void - { - styles[styleProp] = newValue; - } - - override flash_proxy function callProperty(name:*, ... parameters):* - { - if(name in this && this[name] is Function) - return (this[name] as Function).apply(null, parameters); - - if(name == 'toString') - return toString(); - } - - override flash_proxy function setProperty(name:*, value:*):void - { - if(name in propertiesMap) - this[name] = value; - else - setStyle(name, value); - } - - override flash_proxy function getProperty(name:*):* - { - if(name in this) - return this[name]; - - return getStyle(name); - } - - override flash_proxy function hasProperty(name:*):Boolean - { - return propertiesMap ? name in propertiesMap : false; - } - - protected var names:Array = []; - - override flash_proxy function nextNameIndex(index:int):int - { - if(index == 0) - { - names.length = 0; - for(var prop:String in styles) - names.push(prop); - } - - if(index < names.length) - return index + 1; - - return 0; - } - - override flash_proxy function nextName(index:int):String - { - return names[index - 1]; - } - - override flash_proxy function nextValue(index:int):* - { - return this[names[index]]; - } - - generatePropertiesMap(new StyleAwareActor()); - - private static var propertiesMap:Object; - - protected static function generatePropertiesMap(typeOf:*):void - { - propertiesMap = {}; - - var type:XML = describeType(typeOf); - var prop:XML; - for each(prop in type..method) - { - propertiesMap[prop.@name] = true; - } - - for each(prop in type..accessor.(@access == "readwrite")) - { - propertiesMap[prop.@name] = true; - } - } - } -} - diff --git a/src/org/tinytlf/decor/ITextDecor.as b/src/org/tinytlf/decor/ITextDecor.as deleted file mode 100644 index 7cb57ed..0000000 --- a/src/org/tinytlf/decor/ITextDecor.as +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.decor -{ - import org.tinytlf.ITextEngine; - import org.tinytlf.layout.ITextContainer; - - public interface ITextDecor - { - function get engine():ITextEngine; - function set engine(textEngine:ITextEngine):void; - - function render():void; - - function removeAll():void; - - function decorate(element:*, styleObj:Object, layer:int = 0, container:ITextContainer = null):void; - function undecorate(element:* = null, decorationProp:String = null):void; - - function mapDecoration(decorationProp:String, decorationClassOrInstance:Object):void; - function unMapDecoration(decorationProp:String):Boolean; - function hasDecoration(decorationProp:String):Boolean; - function getDecoration(decorationProp:String, container:ITextContainer = null):ITextDecoration; - } -} - diff --git a/src/org/tinytlf/decor/ITextDecoration.as b/src/org/tinytlf/decor/ITextDecoration.as deleted file mode 100644 index c8eb411..0000000 --- a/src/org/tinytlf/decor/ITextDecoration.as +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.decor -{ - import flash.display.Sprite; - import flash.events.IEventDispatcher; - import flash.geom.Rectangle; - - import org.tinytlf.ITextEngine; - import org.tinytlf.core.IStyleAware; - import org.tinytlf.layout.ITextContainer; - - public interface ITextDecoration extends IStyleAware, IEventDispatcher - { - function get containers():Vector. - function set containers(textContainers:Vector.):void; - - function get engine():ITextEngine; - function set engine(textEngine:ITextEngine):void; - - function setup(...args):Vector. - - function draw(bounds:Vector., layer:int = 0):void; - } -} - diff --git a/src/org/tinytlf/decor/TextDecor.as b/src/org/tinytlf/decor/TextDecor.as deleted file mode 100644 index 9af526f..0000000 --- a/src/org/tinytlf/decor/TextDecor.as +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.decor -{ - import flash.utils.Dictionary; - - import org.tinytlf.ITextEngine; - import org.tinytlf.layout.ITextContainer; - - public class TextDecor implements ITextDecor - { - public function TextDecor() - { - } - - protected var _engine:ITextEngine; - - public function get engine():ITextEngine - { - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine == _engine) - return; - - _engine = textEngine; - } - - public function render():void - { - var i:int = 0; - var n:int = layers.length; - var layer:Dictionary; - var element:*; - var decorationProp:String; - var decoration:ITextDecoration; - - for(; i < n; ++i) - { - layer = layers[i]; - for(element in layer) - for each(decoration in layer[element]) - if(decoration) - decoration.draw(decoration.setup(element), i); - } - } - - public function removeAll():void - { - for(var layer:* in layers) - { - for(var element:* in layers[layer]) - { - for(var decoration:String in layers[layer][element]) - { - delete layers[layer][element][decoration] - } - delete layers[layer][element]; - } - delete layers[layer]; - } - - engine.invalidateDecorations(); - } - - protected var layers:Array = []; - - /** - * Decorate dresses up an element for presentation. Based on the parameters - * passed in, he can do many things. - * - * This also allows decorations to be drawn to different layers, so you can - * have decorations that are assured to be at different Z-indicies. 0 is the - * top most layer, with each increment from 0 -> Infinity on a 'lower' level - * in the displayList. An object on layer 0 will draw on top of an object at - * layer 1, etc. - * - * One of the simplest use cases is setting a decoration property on an - * element with a value. So, in order to set a background color of red - * (assuming there's a decoration mapped for 'backgroundColor'), you would call: - * #decorate(myElement, 'backgroundColor', 0xFF0000); - * - * If, instead of passing a value for the decoration, you want the value to - * be looked up through styles, you can pass a styleName or style Object with - * key/value pairs. So, say you had this style: - * - * myElementOverStyle { - * backgroundColor: #FF0000; - * backgroundAlpha: 0.25; - * } - * - * You could call this: - * #decorate(myElement, 'backgroundColor', null, 0, 'myElementOverStyle'); - * - * If you don't have a style defined, no worries. Just pass an object with - * the values: - * #decorate(myElement, 'backgroundColor', null, 0, {backgroundColor:#FF0000, backgroundAlpha:0.25}); - * - * This sets the properties in the object as styles on the decoration. - * - * Alternatively, you can pass in no decoration property and no value, just a - * styleName or a key/value Object, and create ITextDecoration - * instances from the style declaration or values in the Object. - * - * I will use this style declaration for reference, but bear in mind that - * this could just as easily be a key/value paired Object: - * myElementStyle { - * backgroundColor: #FF0000; - * backgroundAlpha: 0.25; - * underline: true; - * underlineColor: #00FF00; - * underlineThickness: 4; - * } - * - * Typically backgroundColor and underline exist, so I will use them as an - * example: - * - * We check the list of mapped decoration names against the properties that - * exist in the style declaration. - * We see that a 'backgroundColor' property exists, which is mapped to a - * BackgroundColorDecoration class. So it creates a new instance of the - * BackgroundColorDecoration class and saddles it with the styleName. - * - * Likewise, it would also match the typical 'underline' style up with the - * UnderlineDecoration class. So he creates an UnderlineDecoration as well, - * passing the styleName along as well. - * - * It's important to note that this, while convenient when creating multiple - * decorations, doesn't allow you the ability of layering your content any - * more than 'first-come, first-served.' - * - */ - public function decorate(element:*, styleObj:Object, layer:int = 0, container:ITextContainer = null):void - { - if(!element || !styleObj) - return; - - //Resolve the layer business first - var theLayer:Object = resolveLayer(layer); - if(!(element in theLayer) || theLayer[element] == null) - theLayer[element] = new Dictionary(true); - - //Don't handle parsing a styleName for now, do that in a subclass. - if(styleObj is String) - return; - - var decoration:ITextDecoration; - - var styleProp:String; - for(styleProp in styleObj) - { - if(hasDecoration(styleProp)) - { - decoration = ITextDecoration(theLayer[element][styleProp] = getDecoration(styleProp, container)); - for(styleProp in styleObj) - decoration.setStyle(styleProp, styleObj[styleProp]); - } - } - - engine.invalidateDecorations(); - } - - /** - * Undecorate can completely dress down the element passed in, or it can strip - * out the decoration for a particular property, leaving the others intact. - */ - public function undecorate(element:* = null, decorationProp:String = null):void - { - var i:int = layers.length - 1; - var layer:Dictionary; - - for(; i >= 0; i--) - { - layer = Dictionary(layers[i]); - - if(!layer) - continue; - - if(element) - { - if(!(element in layer) || layer[element] == null) - continue; - - if(!decorationProp) - for(var dec:String in layer[element]) - delete layer[element][dec]; - else if(decorationProp in layer[element]) - delete layer[element][decorationProp]; - - if(isEmpty(layer[element])) - delete layer[element]; - } - else if(decorationProp) - { - for(var e:* in layer) - { - if(layer[e] == null) - continue; - - if(decorationProp in layer[e]) - delete layer[e][decorationProp]; - - if(isEmpty(layer[e])) - delete layer[e]; - } - } - } - - engine.invalidateDecorations(); - } - - private var decorationsMap:Object = {}; - - public function mapDecoration(styleProp:String, decorationClassOrInstance:Object):void - { - decorationsMap[styleProp] = decorationClassOrInstance; - } - - public function unMapDecoration(styleProp:String):Boolean - { - if(!hasDecoration(styleProp)) - return false; - - return delete decorationsMap[styleProp]; - } - - public function hasDecoration(decorationProp:String):Boolean - { - return Boolean(decorationProp in decorationsMap); - } - - public function getDecoration(styleProp:String, container:ITextContainer = null):ITextDecoration - { - if(!hasDecoration(styleProp)) - return null; - - var decoration:* = decorationsMap[styleProp]; - if(decoration is Class) - decoration = ITextDecoration(new decoration()); - if(decoration is Function) - decoration = ITextDecoration((decoration as Function)()); - - if(!decoration) - return null; - - var vec:Vector. = new Vector.(); - if(container) - vec.push(container); - - ITextDecoration(decoration).containers = vec; - - ITextDecoration(decoration).engine = engine; - - return ITextDecoration(decoration); - } - - protected function resolveLayer(layer:int):Dictionary - { - if(layer < 0) - layer = 0; - // Allow them to use the larger layer, but keep the Array densly populated. - // This helps with performance, but also solves the bug where a coder really - // does want to put something at higher level before other levels are created. - // Originally I kept the layers within the bounds of the array, but that - // introduced race-condition-y scanerios. - else if(layer > layers.length) - { - var i:int = layers.length - 1; - while(++i < layer) - layers[i] = (i in layers) ? layers[i] : null; - } - - if(!(layers[layer])) - layers[layer] = new Dictionary(true); - - return Dictionary(layers[layer]); - } - - private function isEmpty(dict:Object):Boolean - { - if(!dict) - return true; - - for(var prop:* in dict) - if(dict[prop]) - return false; - - return true; - } - } -} - diff --git a/src/org/tinytlf/decor/TextDecoration.as b/src/org/tinytlf/decor/TextDecoration.as deleted file mode 100644 index a837d27..0000000 --- a/src/org/tinytlf/decor/TextDecoration.as +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.decor -{ - import flash.display.DisplayObject; - import flash.display.DisplayObjectContainer; - import flash.display.Sprite; - import flash.events.Event; - import flash.events.EventDispatcher; - import flash.geom.Rectangle; - import flash.text.engine.ContentElement; - import flash.text.engine.GroupElement; - import flash.text.engine.TextBlock; - import flash.text.engine.TextLine; - import flash.text.engine.TextLineMirrorRegion; - import flash.text.engine.TextLineValidity; - import flash.utils.Dictionary; - import flash.utils.flash_proxy; - - import org.tinytlf.ITextEngine; - import org.tinytlf.core.StyleAwareActor; - import org.tinytlf.interaction.LineEventInfo; - import org.tinytlf.layout.ITextContainer; - - use namespace flash_proxy; - - public class TextDecoration extends StyleAwareActor implements ITextDecoration - { - public function TextDecoration(styleName:String = "") - { - super(styleName); - } - - private var _containers:Vector.; - public function get containers():Vector. - { - return _containers; - } - - public function set containers(textContainers:Vector.):void - { - if(textContainers === _containers) - return; - - _containers = textContainers; - } - - protected var _engine:ITextEngine; - - public function get engine():ITextEngine - { - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine == _engine) - return; - - _engine = textEngine; - } - - private var derivedParent:DisplayObjectContainer; - - public function setup(... args):Vector. - { - var bounds:Vector. = new Vector.(); - - if(args.length <= 0) - return bounds; - - var arg:* = args[0]; - - var uiLineClass:Class = LineEventInfo.uiLineClass; - - if(arg is ContentElement) - { - var element:ContentElement = arg as ContentElement; - var regions:Vector. = getMirrorRegions(element); - if(regions.length <= 0) - return bounds; - - var region:TextLineMirrorRegion; - var rect:Rectangle; - var line:DisplayObjectContainer; - - - while(regions.length > 0) - { - region = regions.pop(); - rect = region.bounds; - - line = (uiLineClass && region.textLine.parent is uiLineClass) ? region.textLine.parent : region.textLine; - - bounds.push(new Rectangle(rect.x + line.x, rect.y + line.y, rect.width, rect.height)); - } - - // @TODO - // When you specify decorations for a ContentElement, there's no way to - // associate it with ITextContainers yet. This is a hack that grabs the - // containers that hold the TextLines that this element is rendered with. - // It might be better if this exists somewhere else, like if ITextLayout - // could update Decor for any decorations that are keyed off ContentElements. - if(containers.length == 0) - { - var lines:Vector. = getTextLines(element); - var container:ITextContainer; - while(lines.length) - { - container = engine.layout.getContainerForLine(lines.shift()); - if(containers.indexOf(container) == -1) - containers.push(container); - } - } - } - else if(arg is TextLine) - { - var tl:TextLine = arg as TextLine; - bounds.push(tl.getBounds(tl.parent)); - } - else if(arg is uiLineClass) - { - var ui:DisplayObject = arg as DisplayObject; - bounds.push(ui.getBounds(ui.parent)); - } - else if(arg is Rectangle) - bounds.push(arg); - else if(arg is Vector.) - bounds = bounds.concat(arg); - - return bounds; - } - - protected var spriteMap:Dictionary = new Dictionary(true); - - public function draw(bounds:Vector., layer:int = 0):void - { - if(!containers) - return; - - spriteMap = new Dictionary(true); - - // 1. Iterate over containers - // 2. Iterate over bounds - // 3. Check intersection - // 4. possibly create sprite -- add it to container.shapes - // 5. associate bounds with proper sprite - - var i:int = 0; - var n:int = containers.length; - var j:int = 0; - var k:int = bounds.length; - - var container:ITextContainer; - var doc:DisplayObjectContainer; - - for(i = 0; i < n; i++) - { - container = containers[i]; - doc = container.target; - - for(j = 0; j < k; j++) - { - if(bounds[j].intersects(doc.getBounds(doc))) - { - if(! (container in spriteMap)) - spriteMap[container] = resolveLayer(container.shapes, layer); - - spriteMap[bounds[j]] = spriteMap[container]; - } - } - } - } - - private function resolveLayer(shapes:Sprite, layer:int):Sprite - { - if(shapes.numChildren > layer) - return Sprite(shapes.getChildAt(layer)); - - var sprite:Sprite; - var i:int = shapes.numChildren - 1; - while(++i <= layer) - sprite = Sprite(shapes.addChildAt(new Sprite(), i)); - - return sprite; - } - - protected function getTextBlock(element:ContentElement):TextBlock - { - if(!element) - return null; - - return element.textBlock; - } - - protected function getTextLines(element:ContentElement):Vector. - { - var lines:Vector. = new Vector.(); - var block:TextBlock = getTextBlock(element); - if(!block) - return lines; - - var firstLine:TextLine = block.getTextLineAtCharIndex(element.textBlockBeginIndex); - lines.push(firstLine); - - var lastLine:TextLine = block.getTextLineAtCharIndex(element.textBlockBeginIndex + element.rawText.length - 1); - - var line:TextLine = firstLine; - while(line != lastLine) - { - line = line.nextLine; - if(line == null) - break; - lines.push(line); - } - - return lines; - } - - protected function getMirrorRegions(element:ContentElement):Vector. - { - var regions:Vector. = new Vector.(); - var lines:Vector. = getTextLines(element); - var region:TextLineMirrorRegion; - - var lineRegions:Vector.; - - if(element is GroupElement) - { - var elem:ContentElement; - var n:int = GroupElement(element).elementCount; - for(var i:int = 0; i < n; i++) - { - elem = GroupElement(element).getElementAt(i); - regions = regions.concat(getMirrorRegions(elem)); - } - } - - var line:TextLine; - - while(lines.length > 0) - { - line = lines.pop(); - if(line.validity != TextLineValidity.VALID) - continue; - - lineRegions = line.mirrorRegions.concat(); - while(lineRegions.length > 0) - { - region = lineRegions.pop(); - if(region.element == element) - regions.push(region); - } - } - - return regions; - } - - protected function getBounds(element:ContentElement):Vector. - { - var bounds:Vector. = new Vector.(); - var regions:Vector.; - - if(element is GroupElement) - { - var elem:ContentElement; - var n:int = GroupElement(element).elementCount; - for(var i:int = 0; i < n; i++) - { - elem = GroupElement(element).getElementAt(i); - regions = getMirrorRegions(elem); - while(regions.length > 0) - bounds.push(regions.pop().bounds); - } - } - else - { - regions = getMirrorRegions(element); - while(regions.length > 0) - bounds.push(regions.pop().bounds); - } - - return bounds; - } - - private var dispatcher:EventDispatcher = new EventDispatcher(); - - public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = true):void - { - dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference); - } - - public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void - { - dispatcher.removeEventListener(type, listener, useCapture); - } - - public function dispatchEvent(event:Event):Boolean - { - return dispatcher.dispatchEvent(event); - } - - public function hasEventListener(type:String):Boolean - { - return dispatcher.hasEventListener(type); - } - - public function willTrigger(type:String):Boolean - { - return dispatcher.willTrigger(type); - } - - //Statically generate a map of the properties in this object - generatePropertiesMap(new TextDecoration()); - } -} - diff --git a/src/org/tinytlf/decor/decorations/BackgroundColorDecoration.as b/src/org/tinytlf/decor/decorations/BackgroundColorDecoration.as deleted file mode 100644 index e7181c2..0000000 --- a/src/org/tinytlf/decor/decorations/BackgroundColorDecoration.as +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.decor.decorations -{ - import flash.display.Sprite; - import flash.geom.Rectangle; - - import org.tinytlf.decor.TextDecoration; - - public class BackgroundColorDecoration extends TextDecoration - { - public function BackgroundColorDecoration(styleName:String = "") - { - super(styleName); - } - - override public function draw(bounds:Vector., layer:int = 0):void - { - super.draw(bounds, layer); - - var rect:Rectangle; - var parent:Sprite; - - while(bounds.length > 0) - { - rect = bounds.pop(); - parent = spriteMap[rect]; - if(!parent) - return; - - parent.graphics.beginFill(getStyle("backgroundColor") || 0x000000, getStyle("backgroundAlpha") || 1); - parent.graphics.drawRect(rect.x, rect.y, rect.width, rect.height); - } - } - } -} - diff --git a/src/org/tinytlf/decor/decorations/CaretDecoration.as b/src/org/tinytlf/decor/decorations/CaretDecoration.as deleted file mode 100644 index 2defda4..0000000 --- a/src/org/tinytlf/decor/decorations/CaretDecoration.as +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.decor.decorations -{ - import flash.geom.Rectangle; - import org.tinytlf.decor.TextDecoration; - - public class CaretDecoration extends TextDecoration - { - public function CaretDecoration(styleName:String = "") - { - super(styleName); - } - - override public function draw(bounds:Vector., layer:int = 0):void - { - super.draw(bounds, layer); - } - } -} - diff --git a/src/org/tinytlf/decor/decorations/SelectionDecoration.as b/src/org/tinytlf/decor/decorations/SelectionDecoration.as deleted file mode 100644 index 8ed26e0..0000000 --- a/src/org/tinytlf/decor/decorations/SelectionDecoration.as +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.decor.decorations -{ - import flash.geom.Point; - import flash.geom.Rectangle; - import flash.text.engine.TextLine; - - public class SelectionDecoration extends BackgroundColorDecoration - { - public function SelectionDecoration() - { - super(".textSelectionStyle"); - } - - override public function setup(...args):Vector. - { - var bounds:Vector. = super.setup(); - - if(args.length != 3) - return bounds; - - var selection:Point = args[0]; - var startLine:TextLine = args[1]; - var endLine:TextLine = args[2]; - - if(!selection || !startLine || !endLine) - return bounds; - - var start:int; - var end:int; - var startBounds:Rectangle; - var endBounds:Rectangle; - var finalBounds:Rectangle; - - var right:Boolean = selection.x <= selection.y; - - // Selection within the same line - if(startLine == endLine) - { - start = selection.x; - end = selection.y; - - if(!right) - start--; - - startBounds = startLine.getAtomBounds(startLine.getAtomIndexAtCharIndex(start)); - endBounds = startLine.getAtomBounds(startLine.getAtomIndexAtCharIndex(end)); - - finalBounds = startBounds.union(endBounds); - finalBounds.x += startLine.x; - finalBounds.y += startLine.y; - - bounds.push(finalBounds); - } - // Selection between lines - else - { - if(right) - { - start = selection.x; - end = startLine.textBlockBeginIndex + startLine.rawTextLength - 1; - } - else - { - start = selection.x - 1; - end = startLine.textBlockBeginIndex; - } - - startBounds = startLine.getAtomBounds(startLine.getAtomIndexAtCharIndex(start)); - endBounds = startLine.getAtomBounds(startLine.getAtomIndexAtCharIndex(end)); - - finalBounds = startBounds.union(endBounds); - finalBounds.x += startLine.x; - finalBounds.y += startLine.y; - - bounds.push(finalBounds); - - var line:TextLine = startLine; - while(true) - { - line = right ? line.nextLine : line.previousLine; - if(line == endLine || line == null) - break; - - finalBounds = line.getBounds(line); - finalBounds.x += line.x; - finalBounds.y += line.y; - - bounds.push(finalBounds); - } - - if(right) - { - start = endLine.textBlockBeginIndex; - end = selection.y; - } - else - { - start = endLine.textBlockBeginIndex + endLine.rawTextLength - 1; - end = selection.y; - } - - startBounds = endLine.getAtomBounds(endLine.getAtomIndexAtCharIndex(start)); - endBounds = endLine.getAtomBounds(endLine.getAtomIndexAtCharIndex(end)); - - finalBounds = startBounds.union(endBounds); - finalBounds.x += endLine.x; - finalBounds.y += endLine.y; - - bounds.push(finalBounds); - } - - return bounds; - } - } -} - diff --git a/src/org/tinytlf/decor/decorations/UnderlineDecoration.as b/src/org/tinytlf/decor/decorations/UnderlineDecoration.as deleted file mode 100644 index c88d89e..0000000 --- a/src/org/tinytlf/decor/decorations/UnderlineDecoration.as +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.decor.decorations -{ - import flash.display.Sprite; - import flash.geom.Point; - import flash.geom.Rectangle; - - import org.tinytlf.decor.TextDecoration; - - public class UnderlineDecoration extends TextDecoration - { - public function UnderlineDecoration(styleName:String = "") - { - super(styleName); - } - - override public function draw(bounds:Vector., layer:int = 0):void - { - super.draw(bounds, layer); - - var underlineDelta:Number = Math.round((getStyle("fontSize") || 12) / 6); - var start:Point; - var end:Point; - var rect:Rectangle; - var parent:Sprite; - - while(bounds.length > 0) - { - rect = bounds.pop(); - parent = spriteMap[rect]; - - start = new Point(rect.x, rect.y + rect.height - underlineDelta); - end = new Point(rect.x + rect.width, rect.y + rect.height - underlineDelta); - - parent.graphics.lineStyle( - getStyle("underlineThickness") || 2, - getStyle("underlineColor") || getStyle("color") || 0x00, - getStyle("underlineAlpha") || 1, - getStyle("pixelHinting") || false, - getStyle("scaleMode") || "normal", - getStyle("caps") || null, - getStyle("joints") || null, - getStyle("miterLimit") || 3); - - parent.graphics.moveTo(start.x, start.y); - parent.graphics.lineTo(end.x, end.y); - parent.graphics.lineStyle(0, 0, 0); - } - } - } -} - diff --git a/src/org/tinytlf/extensions/fcss/xhtml/core/FStyleProxy.as b/src/org/tinytlf/extensions/fcss/xhtml/core/FStyleProxy.as deleted file mode 100644 index 0434d52..0000000 --- a/src/org/tinytlf/extensions/fcss/xhtml/core/FStyleProxy.as +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.fcss.xhtml.core -{ - import com.flashartofwar.fcss.stylesheets.FStyleSheet; - - import org.tinytlf.core.StyleAwareActor; - - public class FStyleProxy extends StyleAwareActor - { - public function FStyleProxy(styleObject:Object=null) - { - super(styleObject); - } - - public var sheet:FStyleSheet; - - override public function set style(value:Object):void - { - if(value is FStyleSheet) - { - sheet = FStyleSheet(value); - return; - } - - super.style = value; - } - - override public function getStyle(styleProp:String):* - { - if(styleProp in styles) - return styles[styleProp]; - else if(sheet) - return sheet.getStyle(styleProp); - } - } -} - diff --git a/src/org/tinytlf/extensions/fcss/xhtml/styles/FCSSTextStyler.as b/src/org/tinytlf/extensions/fcss/xhtml/styles/FCSSTextStyler.as deleted file mode 100644 index 7bd7b30..0000000 --- a/src/org/tinytlf/extensions/fcss/xhtml/styles/FCSSTextStyler.as +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.fcss.xhtml.styles -{ - import com.flashartofwar.fcss.styles.IStyle; - import com.flashartofwar.fcss.stylesheets.FStyleSheet; - import com.flashartofwar.fcss.utils.TypeHelperUtil; - - import flash.text.engine.*; - import flash.utils.Dictionary; - - import org.tinytlf.extensions.fcss.xhtml.core.FStyleProxy; - import org.tinytlf.styles.TextStyler; - - public class FCSSTextStyler extends TextStyler - { - override public function set style(value:Object):void - { - var sheet:FStyleSheet = new FStyleSheet(); - sheet.parseCSS(value as String); - super.style = new FStyleProxy(sheet); - } - - private var nodeCache:Dictionary = new Dictionary(true); - - override public function getElementFormat(element:*):ElementFormat - { - if(!(element is Array) || !(style is FStyleProxy)) - return super.getElementFormat(element); - - // Context is an array of XML nodes, the currently processing - // node and its parents, along with all their attributes. - var context:Array = (element as Array); - - var node:XML; - var attributes:XMLList; - var attr:String; - - var i:int = 0; - var n:int = context.length; - var j:int = 0; - var k:int = 0; - - var className:String; - var idName:String; - var uniqueNodeName:String; - var inlineStyle:String = 'a:a;'; - var inheritanceStructure:Array = ['global']; - - var fStyle:IStyle; - var str:String = ''; - - for(i = 0; i < n; i++) - { - node = context[i]; - attributes = node.attributes(); - - if(!(node in nodeCache)) - { - k = attributes.length(); - if(node.localName()) - str += node.localName(); - - if(k > 0) - { - // Math.random() * one billion. reasonably safe for unique identifying... - uniqueNodeName = ' ' + node.localName() + String(Math.round(Math.random() * 100000000000)); - - // + "style{"; // + attributes['style'] + "}"; - for(j = 0; j < k; j++) - { - attr = attributes[j].name(); - - if(attr == 'class') - className = attributes[j]; - else if(attr == 'id') - idName = attributes[j]; - else if(attr == 'style') - inlineStyle += attributes[j]; - else - inlineStyle += (attr + ": " + attributes[j] + ";"); - } - - if(className) - str += " ." + className; - if(idName) - str += " #" + idName; - - str += uniqueNodeName; - - FStyleProxy(style).sheet.parseCSS(uniqueNodeName+ '{' + inlineStyle + '}'); - } - - nodeCache[node] = str; - } - else - { - str = nodeCache[node]; - } - - inheritanceStructure = inheritanceStructure.concat(str.split(' ')); - - str = ''; - className = ''; - idName = ''; - uniqueNodeName = ''; - inlineStyle = 'a:a;'; - } - - fStyle = FStyleProxy(style).sheet.getStyle.apply(null, inheritanceStructure); - - var format:ElementFormat = new ElementFormat( - new FontDescription( - TypeHelperUtil.getType(fStyle["fontName"] || "_sans", 'string'), - TypeHelperUtil.getType(fStyle["fontWeight"] || FontWeight.NORMAL, 'string'), - TypeHelperUtil.getType(fStyle["fontStyle"] || FontPosture.NORMAL, 'string'), - TypeHelperUtil.getType(fStyle["fontLookup"] || FontLookup.DEVICE, 'string'), - TypeHelperUtil.getType(fStyle["renderingMode"] || RenderingMode.CFF, 'string'), - TypeHelperUtil.getType(fStyle["cffHinting"] || CFFHinting.HORIZONTAL_STEM, 'string') - ), - TypeHelperUtil.getType(fStyle["fontSize"] || '12', 'int'), - TypeHelperUtil.getType(fStyle["color"] || '0x0', 'uint'), - TypeHelperUtil.getType(fStyle["fontAlpha"] || '1', 'number'), - TypeHelperUtil.getType(fStyle["textRotation"] || TextRotation.AUTO, 'string'), - TypeHelperUtil.getType(fStyle["dominantBaseLine"] || TextBaseline.ROMAN, 'string'), - TypeHelperUtil.getType(fStyle["alignmentBaseLine"] || TextBaseline.USE_DOMINANT_BASELINE, 'string'), - TypeHelperUtil.getType(fStyle["baseLineShift"] || '0.0', 'number'), - TypeHelperUtil.getType(fStyle["kerning"] || Kerning.ON, 'string'), - TypeHelperUtil.getType(fStyle["trackingRight"] || '0.0', 'number'), - TypeHelperUtil.getType(fStyle["trackingLeft"] || '0.0', 'number'), - TypeHelperUtil.getType(fStyle["locale"] || "en", 'string'), - TypeHelperUtil.getType(fStyle["breakOpportunity"] || BreakOpportunity.AUTO, 'string'), - TypeHelperUtil.getType(fStyle["digitCase"] || DigitCase.DEFAULT, 'string'), - TypeHelperUtil.getType(fStyle["digitWidth"] || DigitWidth.DEFAULT, 'string'), - TypeHelperUtil.getType(fStyle["ligatureLevel"] || LigatureLevel.COMMON, 'string'), - TypeHelperUtil.getType(fStyle["typographicCase"] || TypographicCase.DEFAULT, 'string')); - - return format; - } - } -} - - diff --git a/src/org/tinytlf/extensions/mx/FlexFTETextArea.as b/src/org/tinytlf/extensions/mx/FlexFTETextArea.as deleted file mode 100644 index 7074753..0000000 --- a/src/org/tinytlf/extensions/mx/FlexFTETextArea.as +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.mx -{ - import flash.display.DisplayObject; - import flash.events.Event; - import flash.events.EventPhase; - import flash.text.engine.TextLine; - - import mx.containers.Canvas; - import mx.core.EdgeMetrics; - import mx.core.IUIComponent; - import mx.core.ScrollPolicy; - import mx.core.mx_internal; - import org.tinytlf.extensions.mx.layout.FlexTextContainer; - - import org.tinytlf.ITextEngine; - import org.tinytlf.decor.decorations.BackgroundColorDecoration; - import org.tinytlf.decor.decorations.StrikeThroughDecoration; - import org.tinytlf.decor.decorations.UnderlineDecoration; - import org.tinytlf.layout.ITextContainer; - - use namespace mx_internal; - - public class FlexFTETextArea extends Canvas - { - public function FlexFTETextArea() - { - super(); - - minWidth = 100; - minHeight = 100; - } - - private var _container:ITextContainer; - - public function get container():ITextContainer - { - if(!_container) - _container = new FlexTextContainer(this); - - return _container; - } - - public function set container(textContainer:ITextContainer):void - { - if(textContainer == _container) - return; - - _container = textContainer; - _container.target = this; - } - - override public function set data(value:Object):void - { - var changed:Boolean = (value !== super.data); - super.data = value; - if(changed) - { - textNeedsRender = true; - invalidateProperties(); - } - } - - private var _engine:ITextEngine; - - public function get engine():ITextEngine - { - if(!_engine) - { - _engine = new FlexTextEngine(stage); - _engine.layout.addContainer(container); - - if(!stage) - addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); - } - - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine == _engine) - return; - - _engine = textEngine; - } - - /** - * @private - * Called just before a line is added to the display list. - */ - protected function hookLine(line:DisplayObject):DisplayObject - { - return line; - } - - /** - * @private - * Called just before rendering of the TextContainer. - */ - protected function hookEngine():void - { - if(styleName is String) - engine.styler.style = String(styleName); - - //Default mapped text decorations. - engine.decor.mapDecoration("backgroundColor", BackgroundColorDecoration); - engine.decor.mapDecoration("underline", UnderlineDecoration); - engine.decor.mapDecoration("strikethrough", StrikeThroughDecoration); - } - - protected function renderText():void - { - hookEngine(); - engine.prerender(); - engine.invalidate(); - engine.render(); - } - - override public function addChildAt(child:DisplayObject, index:int):DisplayObject - { - if(child is IUIComponent) - return super.addChildAt(child is UITextLine ? hookLine(child) : child, index); - - return rawChildren_addChildAt(child is TextLine ? hookLine(child) : child, index); - } - - protected var textNeedsRender:Boolean = false; - - override protected function commitProperties():void - { - super.commitProperties(); - - if(!stage && textNeedsRender) - { - textNeedsRender = true; - addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); - return; - } - - if(textNeedsRender) - { - if(isSizeSpecialCase && isNaN(lastUnscaledWidth)) - { - invalidateDisplayList(); - return; - } - - // Create the TextBlocks/Render the TextLines. - var vm:EdgeMetrics = viewMetricsAndPadding; - var w:Number = lastUnscaledWidth - vm.left - vm.right; - w -= (verticalScrollPolicy == ScrollPolicy.ON || (verticalScrollPolicy == ScrollPolicy.AUTO && verticalScrollBar)) ? - verticalScrollBar.getExplicitOrMeasuredWidth() : 0; - - container.allowedWidth = w; - - engine.blockFactory.data = data; - - renderText(); - - invalidateSize(); - invalidateDisplayList(); - - textNeedsRender = false; - } - } - - protected var lastUnscaledWidth:Number = NaN; - - override protected function updateDisplayList(w:Number, h:Number):void - { - if(isSizeSpecialCase) - { - var firstTime:Boolean = isNaN(lastUnscaledWidth) || lastUnscaledWidth != w; - lastUnscaledWidth = w; - if(firstTime) - { - invalidateProperties(); - return; - } - } - - lastUnscaledWidth = w; - - if(textNeedsRender) - invalidateProperties(); - - super.updateDisplayList(w, h); - } - - /** - * @private - * The cases that requires a second pass through the LayoutManager - * are (the control is to use the percentWidth - * but the measuredHeight) and - * (the control is to use the parent's width minus the constraints - * but the measuredHeight). - */ - protected function get isSizeSpecialCase():Boolean - { - var left:Number = getStyle("left"); - var right:Number = getStyle("right"); - - return (!isNaN(percentWidth) || (!isNaN(left) && !isNaN(right))) && isNaN(explicitHeight) && isNaN(percentHeight); - } - - protected function onAddedToStage(event:Event):void - { - if(event.eventPhase != EventPhase.AT_TARGET) - return; - - removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); - - engine.stage = stage; - - if(textNeedsRender) - invalidateProperties(); - } - } -} - diff --git a/src/org/tinytlf/extensions/mx/FlexTextEngine.as b/src/org/tinytlf/extensions/mx/FlexTextEngine.as deleted file mode 100644 index af4e62d..0000000 --- a/src/org/tinytlf/extensions/mx/FlexTextEngine.as +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.mx -{ - import flash.display.Stage; - - import org.tinytlf.extensions.mx.decor.FlexTextDecor; - import org.tinytlf.extensions.mx.styles.FlexTextStyler; - - import org.tinytlf.TextEngine; - import org.tinytlf.decor.ITextDecor; - import org.tinytlf.styles.ITextStyler; - - public class FlexTextEngine extends TextEngine - { - public function FlexTextEngine(stage:Stage) - { - super(stage); - } - - override public function get decor():ITextDecor - { - if(!_decor) - _decor = new FlexTextDecor(); - - _decor.engine = this; - - return _decor; - } - - override public function get styler():ITextStyler - { - if(!_styler) - _styler = new FlexTextStyler(); - - _styler.engine = this; - - return _styler; - } - } -} - diff --git a/src/org/tinytlf/extensions/mx/UITextLine.as b/src/org/tinytlf/extensions/mx/UITextLine.as deleted file mode 100644 index 5a0309f..0000000 --- a/src/org/tinytlf/extensions/mx/UITextLine.as +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.mx -{ - import flash.display.Graphics; - import flash.text.engine.TextLine; - - import mx.core.UIComponent; - - public class UITextLine extends UIComponent - { - public function UITextLine(line:TextLine = null) - { - this.line = line; - } - - private var _line:TextLine; - public function get line():TextLine - { - return _line; - } - - public function set line(value:TextLine):void - { - if(value === _line) - return; - - if(line) - uninstallLine(line); - - _line = value; - - if(line) - installLine(line); - } - - protected function installLine(line:TextLine):void - { - width = line.width; - line.y = height = line.height; - - addChild(line); - } - - protected function uninstallLine(line:TextLine):void - { - if(contains(line)) - removeChild(line); - - graphics.clear(); - } - - override protected function updateDisplayList(w:Number, h:Number):void - { - super.updateDisplayList(w, h); - - // Draw a mouse catcher behind the TextLine so we can catch and proxy the - // "roll" events. - var g:Graphics = graphics; - g.clear(); - g.beginFill(0x00, 0); - g.drawRect(line.x, 5, w, h); - } - } -} - diff --git a/src/org/tinytlf/extensions/mx/core/FlexStyleProxy.as b/src/org/tinytlf/extensions/mx/core/FlexStyleProxy.as deleted file mode 100644 index 6d3803e..0000000 --- a/src/org/tinytlf/extensions/mx/core/FlexStyleProxy.as +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.mx.core -{ - import mx.core.Singleton; - import mx.styles.CSSStyleDeclaration; - import mx.styles.IStyleManager2; - - import org.tinytlf.core.StyleAwareActor; - - public class FlexStyleProxy extends StyleAwareActor - { - public function FlexStyleProxy(styleObject:Object = null) - { - super(styleObject); - } - - protected var css:CSSStyleDeclaration; - - override public function set style(value:Object):void - { - super.style = value; - - if(value is String) - { - var name:String = String(value); - if(name.indexOf(".") != 0) - name = "." + name; - - css = new CSSStyleDeclaration(name); - for(var s:String in styles) - css.setStyle(s, styles[s]); - } - } - - override public function getStyle(styleProp:String):* - { - if(styleProp in styles) - return styles[styleProp]; - else if(css) - return css.getStyle(styleProp); - } - - override public function setStyle(styleProp:String, newValue:*):void - { - styles[styleProp] = newValue; - if(css) - css.setStyle(styleProp, newValue); - } - - protected function get styleManager():IStyleManager2 - { - return Singleton.getInstance("mx.styles::IStyleManager2") as IStyleManager2; - } - } -} - diff --git a/src/org/tinytlf/extensions/mx/decor/FlexTextDecor.as b/src/org/tinytlf/extensions/mx/decor/FlexTextDecor.as deleted file mode 100644 index a2d8a0e..0000000 --- a/src/org/tinytlf/extensions/mx/decor/FlexTextDecor.as +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.mx.decor -{ - import org.tinytlf.decor.ITextDecoration; - import org.tinytlf.decor.TextDecor; - import org.tinytlf.layout.ITextContainer; - import org.tinytlf.extensions.mx.core.FlexStyleProxy; - - public class FlexTextDecor extends TextDecor - { - public function FlexTextDecor() - { - super(); - } - - override public function decorate(element:*, styleObj:Object, layer:int=0, container:ITextContainer=null):void - { - if(styleObj is String) - styleObj = new FlexStyleProxy(String(styleObj)); - - super.decorate(element, styleObj, layer, container); - } - - override public function getDecoration(styleProp:String, container:ITextContainer = null):ITextDecoration - { - var dec:ITextDecoration = super.getDecoration(styleProp, container); - - //Hook this decoration into the Flex StyleManager - if(dec) - dec.style = new FlexStyleProxy(dec.style); - - return dec; - } - } -} - diff --git a/src/org/tinytlf/extensions/mx/layout/FlexTextContainer.as b/src/org/tinytlf/extensions/mx/layout/FlexTextContainer.as deleted file mode 100644 index d34844d..0000000 --- a/src/org/tinytlf/extensions/mx/layout/FlexTextContainer.as +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.mx.layout -{ - import flash.display.DisplayObject; - import flash.display.DisplayObjectContainer; - import flash.text.engine.TextLine; - - import org.tinytlf.extensions.mx.UITextLine; - - import org.tinytlf.layout.TextContainerBase; - - public class FlexTextContainer extends TextContainerBase - { - public function FlexTextContainer(container:DisplayObjectContainer, width:Number = NaN, height:Number = NaN) - { - super(container, width, height); - } - - override protected function hookLine(line:TextLine):DisplayObjectContainer - { - return new UITextLine(line); - } - - override public function hasLine(line:TextLine):Boolean - { - var child:DisplayObject; - var n:int = target.numChildren; - - for(var i:int = 0; i < n; i++) - { - child = target.getChildAt(i); - - if(child is UITextLine && DisplayObjectContainer(child).contains(line)) - return true; - } - - return false; - } - } -} - diff --git a/src/org/tinytlf/extensions/mx/styles/FlexTextStyler.as b/src/org/tinytlf/extensions/mx/styles/FlexTextStyler.as deleted file mode 100644 index 5ad79ae..0000000 --- a/src/org/tinytlf/extensions/mx/styles/FlexTextStyler.as +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.mx.styles -{ - import flash.text.engine.*; - - import mx.core.Singleton; - import mx.styles.CSSStyleDeclaration; - import mx.styles.IStyleManager2; - - import org.tinytlf.extensions.mx.core.FlexStyleProxy; - import org.tinytlf.styles.TextStyler; - - public class FlexTextStyler extends TextStyler - { - public function FlexTextStyler() - { - super(); - style = new FlexStyleProxy(); - } - - private var css:CSSStyleDeclaration; - - override public function set style(value:Object):void - { - super.style = value; - - if(value is String) - { - var name:String = String(value); - if(name.indexOf(".") != 0) - name = "." + name; - - css = new CSSStyleDeclaration(name); - - for(var s:String in styles) - css.setStyle(s, styles[s]); - } - } - - override public function getElementFormat(element:*):ElementFormat - { - var mainStyleDeclaration:CSSStyleDeclaration = css || new CSSStyleDeclaration(); - var elementStyleDeclaration:CSSStyleDeclaration = styleManager.getStyleDeclaration(styleMap[element] || "") || new CSSStyleDeclaration(); - - var reduceBoilerplate:Function = function(style:String, defaultValue:*):* - { - return (elementStyleDeclaration.getStyle(style) || mainStyleDeclaration.getStyle(style) || defaultValue); - } - - return new ElementFormat( - new FontDescription( - reduceBoilerplate("fontFamily", "_sans"), - reduceBoilerplate("fontWeight", FontWeight.NORMAL), - reduceBoilerplate("fontStyle", FontPosture.NORMAL), - reduceBoilerplate("fontLookup", FontLookup.DEVICE), - reduceBoilerplate("renderingMode", RenderingMode.CFF), - reduceBoilerplate("cffHinting", CFFHinting.HORIZONTAL_STEM) - ), - reduceBoilerplate("fontSize", 12), - reduceBoilerplate("color", 0x0), - reduceBoilerplate("fontAlpha", 1), - reduceBoilerplate("textRotation", TextRotation.AUTO), - reduceBoilerplate("dominantBaseLine", TextBaseline.ROMAN), - reduceBoilerplate("alignmentBaseLine", TextBaseline.USE_DOMINANT_BASELINE), - reduceBoilerplate("baseLineShift", 0.0), - reduceBoilerplate("kerning", Kerning.ON), - reduceBoilerplate("trackingRight", 0.0), - reduceBoilerplate("trackingLeft", 0.0), - reduceBoilerplate("locale", "en"), - reduceBoilerplate("breakOpportunity", BreakOpportunity.AUTO), - reduceBoilerplate("digitCase", DigitCase.DEFAULT), - reduceBoilerplate("digitWidth", DigitWidth.DEFAULT), - reduceBoilerplate("ligatureLevel", LigatureLevel.COMMON), - reduceBoilerplate("typographicCase", TypographicCase.DEFAULT) - ); - } - - protected function get styleManager():IStyleManager2 - { - return Singleton.getInstance("mx.styles::IStyleManager2") as IStyleManager2; - } - } -} - diff --git a/src/org/tinytlf/extensions/xhtml/interaction/AnchorInteractor.as b/src/org/tinytlf/extensions/xhtml/interaction/AnchorInteractor.as deleted file mode 100644 index 7c56487..0000000 --- a/src/org/tinytlf/extensions/xhtml/interaction/AnchorInteractor.as +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.xhtml.interaction -{ - import flash.events.IEventDispatcher; - import flash.events.MouseEvent; - import flash.net.URLRequest; - import flash.net.navigateToURL; - import flash.text.engine.ContentElement; - import flash.ui.Mouse; - import flash.ui.MouseCursor; - - import org.tinytlf.ITextEngine; - import org.tinytlf.decor.ITextDecor; - import org.tinytlf.interaction.LineEventInfo; - import org.tinytlf.interaction.TextDispatcherBase; - - public class AnchorInteractor extends TextDispatcherBase - { - public function AnchorInteractor(target:IEventDispatcher=null) - { - super(target); - } - - override protected function onRollOver(event:MouseEvent):void - { - super.onRollOver(event); - - Mouse.cursor = MouseCursor.BUTTON; - } - - override protected function onRollOut(event:MouseEvent):void - { - super.onRollOut(event); - - Mouse.cursor = MouseCursor.ARROW; - } - - override protected function onMouseMove(event:MouseEvent):void - { - super.onMouseMove(event); - - Mouse.cursor = MouseCursor.BUTTON; - } - - override protected function onClick(event:MouseEvent):void - { - super.onClick(event); - - if(!allowEvent) - return; - - var info:LineEventInfo = LineEventInfo.getInfo(event, this); - - // If the Text Engine is in the middle of rendering lines, info will - // be null. Don't try to mess w/ stuff while we're rendering :) - if(!info) - return; - - var engine:ITextEngine = info.engine; - var element:ContentElement = info.element; - - var obj:Object = element.userData - if(!obj || !('link' in obj)) - return; - - navigateToURL(new URLRequest(obj['link']), obj['target'] || '_blank'); - } - } -} - diff --git a/src/org/tinytlf/extensions/xhtml/layout/adapter/AnchorAdapter.as b/src/org/tinytlf/extensions/xhtml/layout/adapter/AnchorAdapter.as deleted file mode 100644 index 9081b1e..0000000 --- a/src/org/tinytlf/extensions/xhtml/layout/adapter/AnchorAdapter.as +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.xhtml.layout.adapter -{ - import flash.text.engine.ContentElement; - import flash.text.engine.GroupElement; - import flash.text.engine.TextElement; - - import org.tinytlf.layout.adapter.ContentElementAdapter; - - public class AnchorAdapter extends ContentElementAdapter - { - public function AnchorAdapter() - { - super(); - } - - override public function execute(data:Object, ... context:Array):ContentElement - { - var element:ContentElement = super.execute.apply(null, [data].concat(context)); - - if(!element) - return null; - - var anchor:XML; - if(context.length) - anchor = context[0]; - - if(anchor) - { - var obj:Object = {}; - obj['link'] = anchor.@href || ''; - obj['target'] = anchor.@target || ''; - element.userData = obj; - } - - return element; - } - } -} - diff --git a/src/org/tinytlf/extensions/xml/layout/factory/XMLBlockFactory.as b/src/org/tinytlf/extensions/xml/layout/factory/XMLBlockFactory.as deleted file mode 100644 index 4e7c824..0000000 --- a/src/org/tinytlf/extensions/xml/layout/factory/XMLBlockFactory.as +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.extensions.xml.layout.factory -{ - import flash.text.engine.ContentElement; - - import org.tinytlf.layout.adapter.IContentElementAdapter; - import org.tinytlf.layout.factory.AbstractLayoutModelFactory; - - public class XMLBlockFactory extends AbstractLayoutModelFactory - { - XML.ignoreWhitespace = false; - - override public function createElements(... args):Vector. - { - var elements:Vector. = super.createElements.apply(null, args); - - if(!(data is String) && !(data is XML)) - return elements; - - var xml:XML = getXML(data); - if(!xml) - return elements; - - ancestorList = []; - - elements.push(getElementForNode(xml)); - - return elements; - } - - protected function getXML(xmlOrString:Object):XML - { - try{ - return XML(xmlOrString); - } - catch(e:Error){ - //If we failed the first time, maybe they passed in a string like this: - // "My string that has nodes without a root." - try{ - return new XML("<_>" + xmlOrString + ""); - } - catch(e:Error){ - //Do nothing now, we've failed. - } - } - - return null; - } - - protected var ancestorList:Array; - - protected function getElementForNode(node:XML):ContentElement - { - if(!node) - return null; - - var parentName:String = node.localName(); - - if(ancestorList.length) - parentName = ancestorList[ancestorList.length - 1].localName(); - - if(node.nodeKind() != "text") - ancestorList.push(new XML(String(node.toXMLString().match(/\<[^\/](.*?)\>/)[0]).replace('>', '/>'))); - - var adapter:IContentElementAdapter = getElementAdapter(parentName); - var content:ContentElement; - - if(node..*.length() > 1) - { - var elements:Vector. = new Vector.(); - for each(var child:XML in node.*) - elements.push(getElementForNode(child)); - - content = adapter.execute.apply(null, [elements].concat(ancestorList)); - ancestorList.pop(); - } - else if(node..*.length() == 1 || node.nodeKind() != 'text') - { - adapter = getElementAdapter(node.localName()); - content = adapter.execute.apply(null, [node.text().toString()].concat(ancestorList)); - ancestorList.pop(); - } - else - content = adapter.execute.apply(null, [node.toString()].concat(ancestorList)); - - return content; - } - } -} - diff --git a/src/org/tinytlf/interaction/ITextInteractor.as b/src/org/tinytlf/interaction/ITextInteractor.as deleted file mode 100644 index 0f0f90a..0000000 --- a/src/org/tinytlf/interaction/ITextInteractor.as +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.interaction -{ - import org.tinytlf.ITextEngine; - - import flash.events.EventDispatcher; - import flash.events.IEventDispatcher; - - public interface ITextInteractor extends IEventDispatcher - { - function get engine():ITextEngine; - function set engine(textEngine:ITextEngine):void; - - /** - * Returns an EventDispatcher for an element name. Typically an - * IModelAdapter calls this when he is creating a ContentElement - * and is ready to specify an eventMirror. - */ - function getMirror(elementName:String = ""):EventDispatcher; - - /** - * Maps a mirror class or instance to an elementName. - * @return True if the mirror was successfully mapped, False - * if it wasn't. - */ - function mapMirror(elementName:String, mirrorClassOrInstance:Object):void; - /** - * Unmaps the mirror class or instance for the given elementName. - * @return True if the mirror was successfully unmapped, or False - * if it wasn't. - */ - function unMapMirror(elementName:String):Boolean; - } -} - diff --git a/src/org/tinytlf/interaction/LineEventInfo.as b/src/org/tinytlf/interaction/LineEventInfo.as deleted file mode 100644 index debecae..0000000 --- a/src/org/tinytlf/interaction/LineEventInfo.as +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.interaction -{ - import flash.display.DisplayObjectContainer; - import flash.events.Event; - import flash.events.EventDispatcher; - import flash.events.MouseEvent; - import flash.text.engine.ContentElement; - import flash.text.engine.TextLine; - import flash.text.engine.TextLineMirrorRegion; - import flash.text.engine.TextLineValidity; - import flash.utils.getDefinitionByName; - - import org.tinytlf.ITextEngine; - import org.tinytlf.layout.ITextContainer; - import org.tinytlf.utils.FTEUtil; - - public class LineEventInfo - { - public static var uiLineClass:Class = checkFlex(); - - protected static function checkFlex():Class - { - try{ - return getDefinitionByName("mx.tinytlf::UITextLine") as Class; - } - catch(e:*){ - } - - return null; - } - - public static function getInfo(event:Event, eventMirror:EventDispatcher = null):LineEventInfo - { - var target:Object = event.target; - var canProcess:Boolean = (target is TextLine) || (uiLineClass && target is uiLineClass); - - if(!canProcess) - return null; - - var line:TextLine = (target as TextLine) || Object(target).line; - - if(line.validity == TextLineValidity.INVALID) - return null; - - var index:int = line.textBlockBeginIndex; - - if(event is MouseEvent) - index = FTEUtil.getAtomIndexAtPoint(MouseEvent(event).stageX, MouseEvent(event).stageY, line); - - var element:ContentElement = FTEUtil.getContentElementAt(line.textBlock.content, index); - var mirrorRegion:TextLineMirrorRegion = line.getMirrorRegion(eventMirror || element.eventMirror); - - return new LineEventInfo(line, line.userData, mirrorRegion, mirrorRegion != null ? mirrorRegion.element : element, line.parent || (target as DisplayObjectContainer)); - } - - public function LineEventInfo(line:TextLine, engine:ITextEngine, mirrorRegion:TextLineMirrorRegion, element:ContentElement, lineParent:DisplayObjectContainer) - { - _engine = engine; - _element = element; - _line = line; - _mirrorRegion = mirrorRegion; - _container = engine.layout.getContainerForLine(line); - _lineParent = lineParent; - } - - private var _container:ITextContainer; - public function get container():ITextContainer - { - return _container; - } - - private var _engine:ITextEngine; - public function get engine():ITextEngine - { - return _engine; - } - - private var _element:ContentElement; - public function get element():ContentElement - { - return _element; - } - - private var _lineParent:DisplayObjectContainer; - public function get lineParent():DisplayObjectContainer - { - return _lineParent; - } - - private var _line:TextLine; - public function get line():TextLine - { - return _line; - } - - private var _mirrorRegion:TextLineMirrorRegion; - public function get mirrorRegion():TextLineMirrorRegion - { - return _mirrorRegion; - } - } -} - diff --git a/src/org/tinytlf/interaction/TextDispatcherBase.as b/src/org/tinytlf/interaction/TextDispatcherBase.as deleted file mode 100644 index 4ee6576..0000000 --- a/src/org/tinytlf/interaction/TextDispatcherBase.as +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.interaction -{ - import flash.events.EventDispatcher; - import flash.events.IEventDispatcher; - import flash.events.KeyboardEvent; - import flash.events.MouseEvent; - import flash.geom.Point; - import flash.utils.getTimer; - - import org.tinytlf.utils.FTEUtil; - - public class TextDispatcherBase extends EventDispatcher - { - public function TextDispatcherBase(target:IEventDispatcher = null) - { - super(target); - - addListeners(this); - } - - public static const UP:uint = 0x0001; - public static const OVER:uint = 0x0002; - public static const DOWN:uint = 0x0004; - - protected static var mouseState:uint = UP; // | DOWN; - protected static var mouseCoords:Point; - - protected function onClick(event:MouseEvent):void - { - mouseState = FTEUtil.updateBits(mouseState, DOWN, false); - mouseState = FTEUtil.updateBits(mouseState, UP); - mouseCoords = new Point(event.stageX, event.stageY); - } - - protected function onRollOver(event:MouseEvent):void - { - mouseState = FTEUtil.updateBits(mouseState, OVER); - mouseCoords = new Point(event.stageX, event.stageY); - } - - protected function onRollOut(event:MouseEvent):void - { - mouseState = FTEUtil.updateBits(mouseState, OVER, false); - mouseCoords = new Point(event.stageX, event.stageY); - } - - protected function onMouseMove(event:MouseEvent):void - { - mouseState = FTEUtil.updateBits(mouseState, OVER); - mouseCoords = new Point(event.stageX, event.stageY); - } - - private static var doubleClickTime:int = 0; - - protected function onMouseDown(event:MouseEvent):void - { - var time:int = getTimer(); - if((time - doubleClickTime) < 500) - mouseState = FTEUtil.updateBits(mouseState, DOWN, false); - else - mouseState = FTEUtil.updateBits(mouseState, DOWN); - - doubleClickTime = time; - - mouseState = FTEUtil.updateBits(mouseState, UP, false); - mouseCoords = new Point(event.stageX, event.stageY); - } - - protected function onMouseUp(event:MouseEvent):void - { - mouseState = FTEUtil.updateBits(mouseState, DOWN, false); - mouseState = FTEUtil.updateBits(mouseState, UP); - mouseCoords = new Point(event.stageX, event.stageY); - } - - protected function onKeyDown(event:KeyboardEvent):void - { - } - - protected function onKeyUp(event:KeyboardEvent):void - { - } - - public function addListeners(target:IEventDispatcher):void - { - target.addEventListener(MouseEvent.MOUSE_OVER, onRollOver, false, 0, true); - target.addEventListener(MouseEvent.MOUSE_OUT, onRollOut, false, 0, true); - - target.addEventListener(MouseEvent.ROLL_OVER, onRollOver, false, 0, true); - target.addEventListener(MouseEvent.ROLL_OUT, onRollOut, false, 0, true); - - target.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove, false, 0, true); - target.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown, false, 0, true); - target.addEventListener(MouseEvent.MOUSE_UP, onMouseUp, false, 0, true); - target.addEventListener(MouseEvent.CLICK, onClick, false, 0, true); - - target.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown, false, 0, true); - target.addEventListener(KeyboardEvent.KEY_UP, onKeyUp, false, 0, true); - } - - public function removeListeners(target:IEventDispatcher):void - { - target.removeEventListener(MouseEvent.MOUSE_OVER, onRollOver); - target.removeEventListener(MouseEvent.MOUSE_OUT, onRollOut); - - target.removeEventListener(MouseEvent.ROLL_OVER, onRollOver); - target.removeEventListener(MouseEvent.ROLL_OUT, onRollOut); - - target.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); - target.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); - target.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); - - target.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); - target.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp); - } - - protected static var eventTime:int = 0; - - protected static function get allowEvent():Boolean - { - var time:int = getTimer(); - var ret:Boolean = (time - eventTime) > 100; - eventTime = time; - return ret; - } - - protected static function get allowFastEvent():Boolean - { - var time:int = getTimer(); - var ret:Boolean = (time - eventTime) > 10; - eventTime = time; - return ret; - } - } -} - diff --git a/src/org/tinytlf/layout/ITextContainer.as b/src/org/tinytlf/layout/ITextContainer.as deleted file mode 100644 index 46bc898..0000000 --- a/src/org/tinytlf/layout/ITextContainer.as +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout -{ - import org.tinytlf.ITextEngine; - - import flash.display.DisplayObjectContainer; - import flash.display.Sprite; - import flash.text.engine.TextBlock; - import flash.text.engine.TextLine; - - public interface ITextContainer - { - function get engine():ITextEngine; - function set engine(textEngine:ITextEngine):void; - - function get target():DisplayObjectContainer; - function set target(textContainer:DisplayObjectContainer):void; - - function get shapes():Sprite; - function set shapes(shapesContainer:Sprite):void; - - function get allowedWidth():Number; - function set allowedWidth(value:Number):void; - - function get allowedHeight():Number; - function set allowedHeight(value:Number):void; - - function get measuredWidth():Number; - function get measuredHeight():Number; - - function clear():void; - function layout(block:TextBlock, line:TextLine):TextLine; - function resetShapes():void; - function hasLine(line:TextLine):Boolean; - } -} - diff --git a/src/org/tinytlf/layout/ITextLayout.as b/src/org/tinytlf/layout/ITextLayout.as deleted file mode 100644 index a4f8ecd..0000000 --- a/src/org/tinytlf/layout/ITextLayout.as +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout -{ - import flash.text.engine.TextBlock; - import flash.text.engine.TextLine; - - import org.tinytlf.ITextEngine; - - public interface ITextLayout - { - function get engine():ITextEngine; - function set engine(textEngine:ITextEngine):void; - - function get containers():Vector.; - - function addContainer(container:ITextContainer):void; - function removeContainer(container:ITextContainer):void; - - function getContainerForLine(line:TextLine):ITextContainer; - - function clear():void; - function resetShapes():void; - function render(blocks:Vector.):void; - } -} - diff --git a/src/org/tinytlf/layout/LayoutProperties.as b/src/org/tinytlf/layout/LayoutProperties.as deleted file mode 100644 index b42d614..0000000 --- a/src/org/tinytlf/layout/LayoutProperties.as +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout -{ - import org.tinytlf.layout.description.TextAlign; - import org.tinytlf.layout.description.TextDirection; - import org.tinytlf.layout.description.TextFloat; - import org.tinytlf.layout.description.TextTransform; - - public class LayoutProperties - { - public function LayoutProperties(props:Object = null) - { - if(!props) - return; - - for(var prop:String in props) - { - if(prop in this) - this[prop] = props[prop] - } - } - - public var associatedItem:*; - - public var width:Number = 0; - public var lineHeight:Number = 0; - public var textIndent:Number = 0; - public var paddingLeft:Number = 0; - public var paddingRight:Number = 0; - public var paddingBottom:Number = 0; - public var paddingTop:Number = 0; - - public var textAlign:TextAlign = TextAlign.LEFT; - public var textDirection:TextDirection = TextDirection.LTR; - public var textTransform:TextTransform = TextTransform.NONE; - public var float:TextFloat = TextFloat.LEFT; - } -} - diff --git a/src/org/tinytlf/layout/TextContainerBase.as b/src/org/tinytlf/layout/TextContainerBase.as deleted file mode 100644 index 9319f4d..0000000 --- a/src/org/tinytlf/layout/TextContainerBase.as +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout -{ - import flash.display.DisplayObjectContainer; - import flash.display.Sprite; - import flash.text.engine.LineJustification; - import flash.text.engine.SpaceJustifier; - import flash.text.engine.TextBlock; - import flash.text.engine.TextLine; - import flash.utils.Dictionary; - - import org.tinytlf.ITextEngine; - import org.tinytlf.layout.description.TextAlign; - - public class TextContainerBase implements ITextContainer - { - public function TextContainerBase(container:DisplayObjectContainer, allowedWidth:Number = NaN, allowedHeight:Number = NaN) - { - this.target = container; - - _allowedWidth = allowedWidth; - _allowedHeight = allowedHeight; - } - - protected var _target:DisplayObjectContainer; - public function get target():DisplayObjectContainer - { - return _target; - } - - public function set target(doc:DisplayObjectContainer):void - { - if(doc == _target) - return; - - _target = doc; - - shapes = Sprite(target.addChild(new Sprite())); - } - - protected var _engine:ITextEngine; - public function get engine():ITextEngine - { - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine == _engine) - return; - - _engine = textEngine; - } - - private var _shapes:Sprite; - public function get shapes():Sprite - { - return _shapes; - } - - public function set shapes(shapesContainer:Sprite):void - { - if(shapesContainer === _shapes) - return; - - var children:Array = prepareShapesChildren(); - - _shapes = shapesContainer; - - while(children.length) - shapes.addChild(children.shift()); - } - - private function prepareShapesChildren():Array - { - var children:Array = []; - if (shapes) - { - children = createArrayOfShapesChildren(); - removeShapesFromParent(); - } - return children; - } - - private function removeShapesFromParent():void - { - if (shapes.parent && shapes.parent.contains(shapes)) - shapes.parent.removeChild(shapes); - } - - private function createArrayOfShapesChildren():Array - { - var children:Array = []; - while (shapes.numChildren) - children.push(shapes.removeChildAt(0)); - return children; - } - - protected var _allowedWidth:Number = NaN; - - public function get allowedWidth():Number - { - return _allowedWidth; - } - - public function set allowedWidth(value:Number):void - { - _allowedWidth = value; - } - - protected var _allowedHeight:Number = NaN; - - public function get allowedHeight():Number - { - return _allowedHeight; - } - - public function set allowedHeight(value:Number):void - { - _allowedHeight = value; - } - - protected var width:Number = 0; - - public function get measuredWidth():Number - { - return width; - } - - protected var height:Number = 0; - - public function get measuredHeight():Number - { - return height; - } - - protected var lines:Dictionary = new Dictionary(false); - - public function hasLine(line:TextLine):Boolean - { - return (line in lines); - } - - public function clear():void - { - for(var line:* in lines) - target.removeChild(line); - - height = 0; - } - - public function resetShapes():void - { - while (shapes && shapes.numChildren) - shapes.removeChildAt(0); - } - - public function layout(block:TextBlock, line:TextLine):TextLine - { - var doc:DisplayObjectContainer; - var props:LayoutProperties = getLayoutProperties(block); - - height += props.paddingTop; - - if(props.textAlign == TextAlign.JUSTIFY) - block.textJustifier = new SpaceJustifier("en", LineJustification.ALL_BUT_LAST, true); - - line = createLine(block, line); - - while(line) - { - width = allowedWidth; - - line.userData = engine; - - doc = hookLine(line); - - height += doc.height; - - doc.y = height; - - target.addChild(doc); - - height += props.lineHeight; - - if(!isNaN(allowedHeight) && measuredHeight > allowedHeight) - return line; - - line = createLine(block, line); - } - - height += props.paddingBottom; - - return line; - } - - protected function createLine(block:TextBlock, line:TextLine = null):TextLine - { - var props:LayoutProperties = getLayoutProperties(block); - - var w:Number = allowedWidth || props.width; - var x:Number = 0; - - if(line == null) - { - w -= props.textIndent; - x += props.textIndent; - } - - w -= props.paddingLeft; - w -= props.paddingRight; - - line = block.createTextLine(line, w); - - if(!line) - return null; - - switch(props.textAlign) - { - case TextAlign.LEFT: - case TextAlign.JUSTIFY: - x += props.paddingLeft; - break; - case TextAlign.CENTER: - x = (width - line.width) * 0.5; - break; - case TextAlign.RIGHT: - x = width - line.width + props.paddingRight; - break; - } - - line.x = x; - - return line; - } - - protected function hookLine(line:TextLine):DisplayObjectContainer - { - lines[line] = true; - return line; - } - - protected function getLayoutProperties(block:TextBlock):LayoutProperties - { - var data:Object = block.userData; - if(data is LayoutProperties) - return LayoutProperties(data); - - return new LayoutProperties(); - } - } -} - diff --git a/src/org/tinytlf/layout/TextLayoutBase.as b/src/org/tinytlf/layout/TextLayoutBase.as deleted file mode 100644 index 84de2f2..0000000 --- a/src/org/tinytlf/layout/TextLayoutBase.as +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout -{ - import flash.text.engine.TextBlock; - import flash.text.engine.TextLine; - - import org.tinytlf.ITextEngine; - - public class TextLayoutBase implements ITextLayout - { - protected var _engine:ITextEngine; - - public function get engine():ITextEngine - { - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine == _engine) - return; - - _engine = textEngine; - } - - public function clear():void - { - for (var i:int = 0; i < containers.length; i++) - { - containers[i].clear(); - } - } - - public function resetShapes():void - { - for (var i:int = 0; i < containers.length; i++) - { - containers[i].resetShapes(); - } - } - - public function render(blocks:Vector.):void - { - if(!containers || !containers.length || !blocks || !blocks.length) - return; - - var blockIndex:int = 0; - var containerIndex:int = 0; - - var block:TextBlock; - var container:ITextContainer; - var line:TextLine; - - while(blockIndex < blocks.length) - { - block = blocks[blockIndex]; - container = containers[containerIndex]; - - line = container.layout(block, line); - - if(line && ++containerIndex < containers.length) - container = containers[containerIndex]; - else if(++blockIndex < blocks.length) - block = blocks[blockIndex]; - else - return; - } - } - - protected var _containers:Vector. = new Vector.; - - public function get containers():Vector. - { - return _containers ? _containers.concat() : new Vector.; - } - - public function addContainer(container:ITextContainer):void - { - if(containers.indexOf(container) != -1) - return; - - _containers.push(container); - container.engine = engine; - } - - public function removeContainer(container:ITextContainer):void - { - var i:int = containers.indexOf(container); - if(i == -1) - return; - - _containers.splice(i, 1); - container.engine = null; - } - - public function getContainerForLine(line:TextLine):ITextContainer - { - var n:int = containers.length; - - for(var i:int = 0; i < n; i++) - { - if(containers[i].hasLine(line)) - return containers[i]; - } - - return null; - } - } -} - diff --git a/src/org/tinytlf/layout/adapter/ContentElementAdapter.as b/src/org/tinytlf/layout/adapter/ContentElementAdapter.as deleted file mode 100644 index 62b36d6..0000000 --- a/src/org/tinytlf/layout/adapter/ContentElementAdapter.as +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.adapter -{ - import flash.events.EventDispatcher; - import flash.text.engine.ContentElement; - import flash.text.engine.ElementFormat; - import flash.text.engine.GroupElement; - import flash.text.engine.TextElement; - - import org.tinytlf.ITextEngine; - - public class ContentElementAdapter implements IContentElementAdapter - { - public function execute(data:Object, ... context:Array):ContentElement - { - var element:ContentElement; - - var name:String = ""; - - if(context.length) - name = context[0].localName(); - - if(data is String) - element = new TextElement(String(data), getElementFormat(context), getEventMirror(name)); - else if(data is Vector.) - element = new GroupElement(Vector.(data), getElementFormat(context), getEventMirror(name)); - - if(!element) - return null; - - //Check to see if there's a styleName for this element - var style:* = engine.styler.getMappedStyle(name); - if(style) - engine.decor.decorate(element, style); - - return element; - } - - private var _engine:ITextEngine; - - public function get engine():ITextEngine - { - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine === _engine) - return; - - _engine = textEngine; - } - - protected function getElementFormat(context:Object):ElementFormat - { - // You can't render a textLine with a null ElementFormat, so return an empty one here. - if(!_engine) - return new ElementFormat(); - - return engine.styler.getElementFormat(context); - } - - protected function getEventMirror(forName:String):EventDispatcher - { - if(!_engine) - return null; - - return engine.interactor.getMirror(forName); - } - } -} - diff --git a/src/org/tinytlf/layout/adapter/IContentElementAdapter.as b/src/org/tinytlf/layout/adapter/IContentElementAdapter.as deleted file mode 100644 index 9359281..0000000 --- a/src/org/tinytlf/layout/adapter/IContentElementAdapter.as +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.adapter -{ - import flash.text.engine.ContentElement; - - import org.tinytlf.ITextEngine; - - public interface IContentElementAdapter - { - function get engine():ITextEngine; - function set engine(textEngine:ITextEngine):void; - - function execute(data:Object, ...context:Array):ContentElement; - } -} - diff --git a/src/org/tinytlf/layout/adapter/ITextBlockAdapter.as b/src/org/tinytlf/layout/adapter/ITextBlockAdapter.as deleted file mode 100644 index fe838c9..0000000 --- a/src/org/tinytlf/layout/adapter/ITextBlockAdapter.as +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.adapter -{ - import flash.text.engine.ContentElement; - import flash.text.engine.TextBlock; - - import org.tinytlf.ITextEngine; - - public interface ITextBlockAdapter - { - function get engine():ITextEngine; - function set engine(textEngine:ITextEngine):void; - - function execute(content:ContentElement, ...context):TextBlock; - } -} - diff --git a/src/org/tinytlf/layout/adapter/TextBlockAdapter.as b/src/org/tinytlf/layout/adapter/TextBlockAdapter.as deleted file mode 100644 index 63cd195..0000000 --- a/src/org/tinytlf/layout/adapter/TextBlockAdapter.as +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.adapter -{ - import flash.text.engine.ContentElement; - import flash.text.engine.TextBlock; - - import org.tinytlf.ITextEngine; - - public class TextBlockAdapter implements ITextBlockAdapter - { - public function execute(content:ContentElement, ...context):TextBlock - { - return new TextBlock(content); - } - - private var _engine:ITextEngine; - - public function get engine():ITextEngine - { - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine === _engine) - return; - - _engine = textEngine; - } - } -} - diff --git a/src/org/tinytlf/layout/description/Enum.as b/src/org/tinytlf/layout/description/Enum.as deleted file mode 100644 index f78ded8..0000000 --- a/src/org/tinytlf/layout/description/Enum.as +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.description -{ - public class Enum - { - public function Enum(identifier:String) - { - _id = identifier; - } - - private var _id:String = ""; - public function get id():String - { - return _id; - } - } -} - diff --git a/src/org/tinytlf/layout/description/TextAlign.as b/src/org/tinytlf/layout/description/TextAlign.as deleted file mode 100644 index 7d4c0d5..0000000 --- a/src/org/tinytlf/layout/description/TextAlign.as +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.description -{ - public class TextAlign extends Enum - { - public function TextAlign(identifier:String) - { - super(id); - } - - public static const LEFT:TextAlign = new TextAlign("left"); - public static const CENTER:TextAlign = new TextAlign("center"); - public static const RIGHT:TextAlign = new TextAlign("right"); - public static const JUSTIFY:TextAlign = new TextAlign("justify"); - } -} - diff --git a/src/org/tinytlf/layout/description/TextDirection.as b/src/org/tinytlf/layout/description/TextDirection.as deleted file mode 100644 index 4f0ceb7..0000000 --- a/src/org/tinytlf/layout/description/TextDirection.as +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.description -{ - public class TextDirection extends Enum - { - public function TextDirection(identifier:String) - { - super(identifier); - } - - public static const LTR:TextDirection = new TextDirection("ltr"); - public static const RTL:TextDirection = new TextDirection("rtl"); - } -} - diff --git a/src/org/tinytlf/layout/description/TextFloat.as b/src/org/tinytlf/layout/description/TextFloat.as deleted file mode 100644 index 163c248..0000000 --- a/src/org/tinytlf/layout/description/TextFloat.as +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.description -{ - public class TextFloat extends Enum - { - public function TextFloat(identifier:String) - { - super(identifier); - } - - public static const LEFT:TextFloat = new TextFloat("left"); - public static const RIGHT:TextFloat = new TextFloat("right"); - } -} - diff --git a/src/org/tinytlf/layout/description/TextTransform.as b/src/org/tinytlf/layout/description/TextTransform.as deleted file mode 100644 index a37ab69..0000000 --- a/src/org/tinytlf/layout/description/TextTransform.as +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.description -{ - public class TextTransform extends Enum - { - public function TextTransform(identifier:String) - { - super(identifier); - } - - public static const CAPITALIZE:TextTransform = new TextTransform('capitalize'); - public static const UPPERCASE:TextTransform = new TextTransform('uppercase'); - public static const LOWERCASE:TextTransform = new TextTransform('lowercase'); - public static const NONE:TextTransform = new TextTransform('none'); - } -} - diff --git a/src/org/tinytlf/layout/factory/AbstractLayoutModelFactory.as b/src/org/tinytlf/layout/factory/AbstractLayoutModelFactory.as deleted file mode 100644 index 6053214..0000000 --- a/src/org/tinytlf/layout/factory/AbstractLayoutModelFactory.as +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.factory -{ - import flash.text.engine.ContentElement; - import flash.text.engine.TextBlock; - import flash.utils.Dictionary; - - import org.tinytlf.ITextEngine; - import org.tinytlf.layout.adapter.ContentElementAdapter; - import org.tinytlf.layout.adapter.IContentElementAdapter; - import org.tinytlf.layout.adapter.ITextBlockAdapter; - import org.tinytlf.layout.adapter.TextBlockAdapter; - - public class AbstractLayoutModelFactory implements ILayoutModelFactory - { - public static const WHITE_SPACE:String = "whitespace"; - public static const GENERIC_TEXT:String = "text"; - - private var _blockAdapter:ITextBlockAdapter; - - public function get blockAdapter():ITextBlockAdapter - { - if(!_blockAdapter) - { - _blockAdapter = new TextBlockAdapter(); - _blockAdapter.engine = engine; - } - - return _blockAdapter; - } - - public function set blockAdapter(adapter:ITextBlockAdapter):void - { - if(adapter === _blockAdapter) - return; - - _blockAdapter = adapter; - _blockAdapter.engine = engine; - } - - private var _data:*; - - public function get data():Object - { - return _data; - } - - public function set data(value:Object):void - { - if(value == _data) - return; - - _data = value; - } - - private var _engine:ITextEngine; - - public function get engine():ITextEngine - { - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine == _engine) - return; - - _engine = textEngine; - } - - protected var _blocks:Vector. = new Vector.(); - - public function get blocks():Vector. - { - return _blocks ? _blocks.concat() : new Vector.; - } - - protected var _elements:Vector.; - - public function get elements():Vector. - { - return _elements ? _elements.concat() : new Vector.; - } - - public function createBlocks(... args):Vector. - { - if(_blocks != null) - { - var block:TextBlock; - while(_blocks.length > 0) - { - block = _blocks.pop(); - block.releaseLines(block.firstLine, block.lastLine); - } - } - else - { - _blocks = new Vector.(); - } - - var elements:Vector. = createElements.apply(null, args); - - while(elements.length > 0) - _blocks.push(blockAdapter.execute(elements.shift())); - - return _blocks; - } - - public function createElements(... args):Vector. - { - _elements = new Vector.(); - return elements; - } - - protected var elementAdapterMap:Dictionary = new Dictionary(false); - - public function getElementAdapter(element:*):IContentElementAdapter - { - var adapter:*; - - //Return the generic adapter if we haven't mapped any. - if(!(element in elementAdapterMap)) - { - adapter = new ContentElementAdapter(); - IContentElementAdapter(adapter).engine = engine; - return adapter; - } - - adapter = elementAdapterMap[element]; - if(adapter is Class) - adapter = IContentElementAdapter(new (adapter as Class)()); - if(adapter is Function) - adapter = IContentElementAdapter((adapter as Function)()); - - IContentElementAdapter(adapter).engine = engine; - - return IContentElementAdapter(adapter); - } - - public function mapElementAdapter(element:*, adapterClassOrInstance:Object):void - { - elementAdapterMap[element] = adapterClassOrInstance; - } - - public function unMapElementAdapter(element:*):Boolean - { - if(!(element in elementAdapterMap)) - return false; - - return delete elementAdapterMap[element]; - } - } -} \ No newline at end of file diff --git a/src/org/tinytlf/layout/factory/ILayoutModelFactory.as b/src/org/tinytlf/layout/factory/ILayoutModelFactory.as deleted file mode 100644 index da8c14c..0000000 --- a/src/org/tinytlf/layout/factory/ILayoutModelFactory.as +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.layout.factory -{ - import flash.text.engine.ContentElement; - import flash.text.engine.TextBlock; - - import org.tinytlf.ITextEngine; - import org.tinytlf.layout.adapter.IContentElementAdapter; - import org.tinytlf.layout.adapter.ITextBlockAdapter; - - /** - * Ultimately the job of BlockFactory is to generate TextBlocks for us. It just - * so happens that generating TextBlocks requires the generation of - * ContentElements as well. Thus this is the main model actor in tinyTLF. - */ - public interface ILayoutModelFactory - { - function get blockAdapter():ITextBlockAdapter; - function set blockAdapter(adapter:ITextBlockAdapter):void; - - function get data():Object; - function set data(value:Object):void; - - function get engine():ITextEngine; - function set engine(textEngine:ITextEngine):void; - - function get blocks():Vector.; - function get elements():Vector.; - - function createBlocks(...args):Vector.; - function createElements(...args):Vector.; - - function getElementAdapter(element:*):IContentElementAdapter; - function mapElementAdapter(element:*, adapterClassOrInstance:Object):void; - function unMapElementAdapter(element:*):Boolean; - } -} - diff --git a/src/org/tinytlf/styles/TextStyler.as b/src/org/tinytlf/styles/TextStyler.as deleted file mode 100644 index 782e6d6..0000000 --- a/src/org/tinytlf/styles/TextStyler.as +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.styles -{ - import flash.text.engine.BreakOpportunity; - import flash.text.engine.CFFHinting; - import flash.text.engine.DigitCase; - import flash.text.engine.DigitWidth; - import flash.text.engine.ElementFormat; - import flash.text.engine.FontDescription; - import flash.text.engine.FontLookup; - import flash.text.engine.FontPosture; - import flash.text.engine.FontWeight; - import flash.text.engine.Kerning; - import flash.text.engine.LigatureLevel; - import flash.text.engine.RenderingMode; - import flash.text.engine.TextBaseline; - import flash.text.engine.TextRotation; - import flash.text.engine.TypographicCase; - import flash.utils.Dictionary; - - import org.tinytlf.ITextEngine; - import org.tinytlf.core.StyleAwareActor; - - public class TextStyler extends StyleAwareActor implements ITextStyler - { - protected var _engine:ITextEngine; - public function get engine():ITextEngine - { - return _engine; - } - - public function set engine(textEngine:ITextEngine):void - { - if(textEngine == _engine) - return; - - _engine = textEngine; - } - - public function getElementFormat(element:*):ElementFormat - { - var reduceBoilerplate:Function = function(style:String, defaultValue:*):* - { - return(getStyle(style) || defaultValue); - } - - return new ElementFormat( - new FontDescription( - reduceBoilerplate("fontName", "_sans"), - reduceBoilerplate("fontWeight", FontWeight.NORMAL), - reduceBoilerplate("fontStyle", FontPosture.NORMAL), - reduceBoilerplate("fontLookup", FontLookup.DEVICE), - reduceBoilerplate("renderingMode", RenderingMode.CFF), - reduceBoilerplate("cffHinting", CFFHinting.HORIZONTAL_STEM) - ), - reduceBoilerplate("fontSize", 12), - reduceBoilerplate("color", 0x0), - reduceBoilerplate("fontAlpha", 1), - reduceBoilerplate("textRotation", TextRotation.AUTO), - reduceBoilerplate("dominantBaseLine", TextBaseline.ROMAN), - reduceBoilerplate("alignmentBaseLine", TextBaseline.USE_DOMINANT_BASELINE), - reduceBoilerplate("baseLineShift", 0.0), - reduceBoilerplate("kerning", Kerning.ON), - reduceBoilerplate("trackingRight", 0.0), - reduceBoilerplate("trackingLeft", 0.0), - reduceBoilerplate("locale", "en"), - reduceBoilerplate("breakOpportunity", BreakOpportunity.AUTO), - reduceBoilerplate("digitCase", DigitCase.DEFAULT), - reduceBoilerplate("digitWidth", DigitWidth.DEFAULT), - reduceBoilerplate("ligatureLevel", LigatureLevel.COMMON), - reduceBoilerplate("typographicCase", TypographicCase.DEFAULT) - ); - } - - protected var styleMap:Dictionary = new Dictionary(true); - - public function getMappedStyle(element:*):* - { - if(element in styleMap) - return styleMap[element]; - - return null; - } - - public function mapStyle(element:*, value:*):void - { - styleMap[element] = value; - } - - public function unMapStyle(element:*):Boolean - { - if(element in styleMap) - return delete styleMap[element]; - - return false; - } - - //Statically generate a map of the properties in this object - generatePropertiesMap(new TextStyler()); - } -} - diff --git a/src/org/tinytlf/utils/EventProxy.as b/src/org/tinytlf/utils/EventProxy.as deleted file mode 100644 index 500d618..0000000 --- a/src/org/tinytlf/utils/EventProxy.as +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.utils -{ - import flash.events.Event; - import flash.events.EventDispatcher; - import flash.events.EventPhase; - import flash.events.IEventDispatcher; - - public class EventProxy extends EventDispatcher - { - private var target:IEventDispatcher; - private var destination:IEventDispatcher; - - public function EventProxy(target:IEventDispatcher, destination:IEventDispatcher) - { - this.target = target; - this.destination = destination; - } - - public function proxyType(type:String):Boolean - { - if(!target) - return false; - - target.addEventListener(type, proxy); - return true; - } - - public function clearType(type:String):Boolean - { - if(!target || !target.hasEventListener(type)) - return false; - - target.removeEventListener(type, proxy); - return true; - } - - private function proxy(event:Event):void - { - if(!destination || event.eventPhase != EventPhase.AT_TARGET) - return; - - destination.dispatchEvent(event.clone()); - } - } -} - diff --git a/src/org/tinytlf/utils/FTEUtil.as b/src/org/tinytlf/utils/FTEUtil.as deleted file mode 100644 index bb0a03f..0000000 --- a/src/org/tinytlf/utils/FTEUtil.as +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.utils -{ - import flash.geom.Point; - import flash.text.engine.ContentElement; - import flash.text.engine.GroupElement; - import flash.text.engine.TextLine; - import flash.ui.Keyboard; - - public class FTEUtil - { - public static function getContentElementAt(element:ContentElement, index:int):ContentElement - { - var idx:int = index; - while(element is GroupElement) - { - idx = index - element.textBlockBeginIndex; - idx = (idx <= 0) ? 1 : (idx < element.rawText.length) ? idx : element.rawText.length - 1 - if(idx < Math.max(element.rawText.length, GroupElement(element).elementCount)) - element = GroupElement(element).getElementAtCharIndex(idx); - else - break; - } - - return element; - } - - public static function getAtomIndexAtPoint(stageX:Number, stageY:Number, line:TextLine):int - { - var atomIndex:int = line.getAtomIndexAtPoint(stageX, stageY); - if(atomIndex == -1) - return -1; - - var atomCenter:int = line.getAtomCenter(atomIndex); - var atomIncrement:int = (line.localToGlobal(new Point(atomCenter, 0)).x <= stageX) ? 1 : 0; - - return line.getAtomTextBlockBeginIndex(atomIndex) + atomIncrement; - } - - public static function getAtomWordBoundary(line:TextLine, element:ContentElement, atomIndex:int, left:Boolean = true):int - { - var adjustedAtomIndex:int = atomIndex + line.textBlockBeginIndex; - - var atomCode:Number = element.rawText.charCodeAt(adjustedAtomIndex); - - if(atomCode === Keyboard.SPACE || (!left && atomIndex == 0)) - { - left ? --atomIndex : ++atomIndex; - adjustedAtomIndex = atomIndex + line.textBlockBeginIndex; - atomCode = element.rawText.charCodeAt(adjustedAtomIndex); - } - - if(left) - { - while(!isNaN(atomCode) && atomIndex > 0 && atomCode != Keyboard.SPACE && line.getAtomGraphic(adjustedAtomIndex) == null) - { - atomCode = element.rawText.charCodeAt(--adjustedAtomIndex); - atomIndex--; - } - } - else - { - while(!isNaN(atomCode) && atomIndex < element.rawText.length && atomCode != Keyboard.SPACE && line.getAtomGraphic(adjustedAtomIndex) == null) - { - atomCode = element.rawText.charCodeAt(++adjustedAtomIndex); - atomIndex++; - } - } - - return atomIndex; - } - - /** - * Returns true if all of the flags specified by flagMask are set. - */ - public static function isBitSet(flags:uint, flagMask:uint):Boolean - { - return flagMask == (flags & flagMask); - } - - /** - * Sets the flags specified by flagMask according to value. - * Returns the new bitflag. - * flagMask can be a combination of multiple flags. - */ - public static function updateBits(flags:uint, flagMask:uint, update:Boolean = true):uint - { - if(update) - { - if((flags & flagMask) == flagMask) - return flags; // Nothing to change - // Don't use ^ since flagMask could be a combination of multiple flags - flags |= flagMask; - } - else - { - if((flags & flagMask) == 0) - return flags; // Nothing to change - // Don't use ^ since flagMask could be a combination of multiple flags - flags &= ~flagMask; - } - return flags; - } - } -} - diff --git a/src/org/tinytlf/utils/KeyboardUtil.as b/src/org/tinytlf/utils/KeyboardUtil.as deleted file mode 100644 index f3240a7..0000000 --- a/src/org/tinytlf/utils/KeyboardUtil.as +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.utils -{ - import flash.ui.Keyboard; - - public class KeyboardUtil - { - public static function isNavigable(keyCode:int):Boolean - { - return( - isArrow(keyCode) || - isRemover(keyCode) || - isWhiteSpace(keyCode) || - isChar(keyCode) - ); - } - - public static function isChar(keyCode:int):Boolean - { - return (!isModifier(keyCode) && !isRemover(keyCode)); - } - - public static function isModifier(keyCode:int):Boolean - { - return( - keyCode == Keyboard.CAPS_LOCK || - keyCode == Keyboard.CONTROL || - keyCode == Keyboard.END || - keyCode == Keyboard.ESCAPE || - keyCode == Keyboard.HOME || - keyCode == Keyboard.INSERT || - keyCode == Keyboard.PAGE_DOWN || - keyCode == Keyboard.PAGE_UP || - keyCode == Keyboard.SHIFT || - isFunction(keyCode) || - isArrow(keyCode) - ); - } - - public static function isWhiteSpace(keyCode:int):Boolean - { - return( - keyCode == Keyboard.NUMPAD_ENTER || - keyCode == Keyboard.ENTER || - keyCode == Keyboard.TAB || - keyCode == Keyboard.SPACE - ); - } - - public static function isArrow(keyCode:int):Boolean - { - return( - keyCode == Keyboard.UP || - keyCode == Keyboard.DOWN || - keyCode == Keyboard.LEFT || - keyCode == Keyboard.RIGHT - ); - } - - public static function isRemover(keyCode:int):Boolean - { - return(keyCode == Keyboard.BACKSPACE || keyCode == Keyboard.DELETE); - } - - public static function isFunction(keyCode:int):Boolean - { - return( - keyCode == Keyboard.F1 || - keyCode == Keyboard.F2 || - keyCode == Keyboard.F3 || - keyCode == Keyboard.F4 || - keyCode == Keyboard.F5 || - keyCode == Keyboard.F6 || - keyCode == Keyboard.F7 || - keyCode == Keyboard.F8 || - keyCode == Keyboard.F9 || - keyCode == Keyboard.F10 || - keyCode == Keyboard.F11 || - keyCode == Keyboard.F12 || - keyCode == Keyboard.F13 || - keyCode == Keyboard.F14 || - keyCode == Keyboard.F15 - ); - } - } -} - diff --git a/src/org/tinytlf/utils/Type.as b/src/org/tinytlf/utils/Type.as deleted file mode 100644 index 3c26eab..0000000 --- a/src/org/tinytlf/utils/Type.as +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.utils -{ - import flash.net.registerClassAlias; - import flash.utils.Dictionary; - import flash.utils.describeType; - import flash.utils.getDefinitionByName; - import flash.utils.getQualifiedClassName; - - /** - * The static Type class contains a collection of type-specific utilities, - * including object reflection. Reflection is described in XML, which - * format is documented in the flash.utils package. XML - * descriptions are cached for performance. - * - * @see flash.utils#describeType - */ - public class Type - { - // global cache improves reflection performance significantly - private static var typeCache:Dictionary = new Dictionary(); - - /** - * Evaluates whether an object or class is derived from a specific - * data type, class or interface. The isType() method is comparable to - * ActionScript's is operator except that it also makes - * class to class evaluations. - * - * @param value The object or class to evaluate. - * @param type The data type to check against. - * - * @return True if the object or class is derived from - * the data type. - */ - public static function isType(value:Object, type:Class):Boolean - { - if(!(value is Class)) - { - return value is type; - } - - if(value == type) - { - return true; - } - - var inheritance:XMLList = describeInheritance(value); - return Boolean(inheritance.(@type == getQualifiedClassName(type)).length() > 0); - } - - /** - * Determines the property type without accessing the property directly. - * Evaluation is based on the property definition and not by its value. - * - * @param value An object or class containing the property - * definition to evaluate. - * @param property The name of the property to be evaluated - * - * @return The class definition of the property type, - * as described by the property definition, or - * null if no definition was found. - */ - public static function getPropertyType(value:Object, property:String):Class - { - if(!(value is Class) && !(property in value)) - { - return null; - } - - // retrieve the correct property from the property list - var propList:XMLList = describeProperties(value).(@name == property); - - return(propList.length() > 0) ? getDefinitionByName(propList[0].@type) as Class : null; - } - - /** - * Ensures a class has a registered alias for object serialization and - * remoting. If the class doesn't yet have an alias it will be - * registered with it's full qualified class name, for example: - * flight.utils.Type. If the class has already been - * assigned an alias then its previous registration will be honored. - * - * @param value The object or class to register. - * - * @return True if the registration was successful, - * otherwise the object already has an alias. - * - * @see flash.net#registerClassAlias - */ - public static function registerType(value:Object):Boolean - { - if(!(value is Class)) - { - value = getType(value); - } - - // if not already registered - var alias:String = describeType(value).@alias; - if(alias.length) - { - return false; - } - - registerClassAlias(getQualifiedClassName(value).split("::").join("."), value as Class); - return true; - } - - /** - * Primary reflection method, producing an XML description of the object - * or class specified. Results are cached internally for performance. - * - * @param value The object or class to introspect. - * @param refreshCache Forces a new description to be generated, - * useful only when a class alias has changed. - * - * @return An XML description, which format is - * documented in the flash.utils - * package. - * - * @see flash.utils#describeType - */ - public static function describeType(value:Object, refreshCache:Boolean = false):XML - { - if(!(value is Class)) - { - value = getType(value); - } - - if(refreshCache || typeCache[value] == null) - { - typeCache[value] = flash.utils.describeType(value); - } - - return typeCache[value]; - } - - /** - * Targeted reflection describing an object's inheritance, including - * extended classes and implemented interfaces. - * - * @param value The object or class to introspect. - * - * @return A list of XML inheritance descriptions. - */ - public static function describeInheritance(value:Object):XMLList - { - return describeType(value).factory.*.(localName() == "extendsClass" || localName() == "implementsInterface"); - } - - /** - * Targeted reflection describing an object's properties, including both - * accessor's (getter/setters) and pure properties. - * - * @param value The object or class to introspect. - * @param metadataType Optional filter to return only those - * property descritions containing the - * specified metadata. - * - * @return A list of XML property descriptions. - */ - public static function describeProperties(value:Object, metadataType:String = null):XMLList - { - var properties:XMLList = describeType(value).factory.*.(localName() == "accessor" || localName() == "variable"); - - return(metadataType == null) ? properties : properties.(child("metadata").(@name == metadataType).length() > 0); - } - - - /** - * Targeted reflection describing an object's methods. - * - * @param value The object or class to introspect. - * @param metadataType Optional filter to return only those - * method descritions containing the - * specified metadata. - * - * @return A list of XML method descriptions. - */ - public static function describeMethods(value:Object, metadataType:String = null):XMLList - { - var methods:XMLList = describeType(value).factory.method; - - return(metadataType == null) ? methods : methods.(child("metadata").(@name == metadataType).length() > 0); - } - - } -} - diff --git a/src/org/tinytlf/utils/getClassName.as b/src/org/tinytlf/utils/getClassName.as deleted file mode 100644 index 3bb3665..0000000 --- a/src/org/tinytlf/utils/getClassName.as +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.utils -{ - import flash.utils.getQualifiedClassName; - - /** - * Returns the exact class name, without package or path. - * - * @param value The object for which the class name is desired. - * Any ActionScript value may be passed including - * all ActionScript types, object instances, - * primitive types such as uint, and class objects. - */ - public function getClassName(value:Object):String - { - return getQualifiedClassName(value).split("::").pop(); - } -} - diff --git a/src/org/tinytlf/utils/getType.as b/src/org/tinytlf/utils/getType.as deleted file mode 100644 index ffebcec..0000000 --- a/src/org/tinytlf/utils/getType.as +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2010 the original author or authors - * - * Permission is hereby granted to use, modify, and distribute this file - * in accordance with the terms of the license agreement accompanying it. - */ -package org.tinytlf.utils -{ - import flash.utils.Proxy; - import flash.utils.getDefinitionByName; - import flash.utils.getQualifiedClassName; - - /** - * Global method for getting an objects type. - * - * @param value The object being evaluated. If the object is a - * class it will be returned as the type and not - * the type Class. - */ - public function getType(value:Object):Class - { - if(value is Class) - { - return value as Class; - } - else if(value is Proxy) - { - return getDefinitionByName(getQualifiedClassName(value)) as Class; - } - else - { - return value.constructor as Class; - } - } -} - diff --git a/test/org/tinytlf/TextEngineTests.as b/test/org/tinytlf/TextEngineTests.as old mode 100644 new mode 100755 index c8e0175..5673797 --- a/test/org/tinytlf/TextEngineTests.as +++ b/test/org/tinytlf/TextEngineTests.as @@ -1,48 +1,44 @@ package org.tinytlf { - + import flash.display.Sprite; import flash.display.Stage; - import flash.events.Event; - import flash.events.TimerEvent; import flash.utils.Timer; - + + import flexunit.framework.Assert; + import mockolate.*; - + import mx.core.UIComponent; - - import org.flexunit.Assert; + import org.flexunit.async.Async; import org.fluint.uiImpersonation.UIImpersonator; - import org.tinytlf.decor.ITextDecor; - import org.tinytlf.decor.TextDecor; - import org.tinytlf.interaction.ITextInteractor; - import org.tinytlf.interaction.TextInteractorBase; - import org.tinytlf.layout.ITextLayout; - import org.tinytlf.layout.TextLayoutBase; - import org.tinytlf.layout.factory.AbstractLayoutModelFactory; - import org.tinytlf.layout.factory.ILayoutModelFactory; - import org.tinytlf.styles.ITextStyler; - import org.tinytlf.styles.TextStyler; - + import org.tinytlf.components.TextFieldEngineConfiguration; + import org.tinytlf.conversion.*; + import org.tinytlf.decor.*; + import org.tinytlf.interaction.*; + import org.tinytlf.layout.*; + import org.tinytlf.styles.*; + public class TextEngineTests { private var engineStage:Stage; private var engine:TextEngine; private var delayTimer:Timer; - + [Before(async, timeout=5000)] public function setup():void { engineStage = UIImpersonator.addChild(new UIComponent()).stage; engine = new TextEngine(engineStage); - delayTimer = new Timer(100, 1); + engine.configuration = new TextFieldEngineConfiguration(); + delayTimer = new Timer(1000, 1); Async.proceedOnEvent(this, - prepare(ITextDecor,ILayoutModelFactory, ITextLayout), - Event.COMPLETE); + prepare(ITextDecor, ITextBlockFactory, ITextLayout), + Event.COMPLETE); } - + [After] public function tearDown():void { @@ -50,228 +46,197 @@ package org.tinytlf engine = null; delayTimer = null; } - + [Test] public function text_engine_constructed():void { Assert.assertTrue(engine != null); } - + //-------------------------------------------------------------------------- // // default properties tests // //-------------------------------------------------------------------------- - + [Test] public function engine_has_default_decor():void { var decor:ITextDecor = engine.decor; - + Assert.assertTrue(decor is TextDecor); } - + [Test] public function engine_has_default_block_factory():void { - var factory:ILayoutModelFactory = engine.blockFactory; - - Assert.assertTrue(factory is AbstractLayoutModelFactory); + var factory:ITextBlockFactory = engine.blockFactory; + + Assert.assertTrue(factory is TextBlockFactoryBase); } - + [Test] public function engine_has_default_interactor():void { var interactor:ITextInteractor = engine.interactor; - + Assert.assertTrue(interactor is TextInteractorBase); } - + [Test] public function engine_has_default_layout():void { var layout:ITextLayout = engine.layout; - + Assert.assertTrue(layout is TextLayoutBase); } - + [Test] public function engine_has_default_styler():void { var styler:ITextStyler = engine.styler; - + Assert.assertTrue(styler is TextStyler) } - + //-------------------------------------------------------------------------- // // public method tests // //-------------------------------------------------------------------------- - - //---------------------------------------------------- - // prerender - //---------------------------------------------------- - - [Test] - public function test_prerender_calls_decor_remove_all():void - { - var decor:ITextDecor = strict(ITextDecor); - stub(decor).method("removeAll"); - stub(decor).setter("engine"); - - engine.decor = decor; - engine.prerender(); - - verify(decor).method("removeAll").once(); - } - - [Test] - public function prerender_calls_block_factory_create_blocks():void - { - var blockFactory:ILayoutModelFactory = nice(ILayoutModelFactory); - stub(blockFactory).method("createBlocks"); - - engine.blockFactory = blockFactory; - engine.prerender(); - - verify(blockFactory).method("createBlocks").once(); - } - + //---------------------------------------------------- // invalidation triggers rendering //---------------------------------------------------- - [Test(async)] - public function invalidate_calls_clear_on_layout_after_current_frame():void - { - var layout:ITextLayout = nice(ITextLayout); - stub(layout).method("clear"); - - engine.layout = layout; - engine.invalidate(); - - Async.handleEvent(this, delayTimer, TimerEvent.TIMER_COMPLETE, - handleInvalidateCallsClearOnLayoutAfterCurrentFrame, 500, layout); - - delayTimer.start(); - } - - private function handleInvalidateCallsClearOnLayoutAfterCurrentFrame(event:Event, layout:ITextLayout):void - { - verify(layout).method("clear").once(); - } - [Test(async)] public function invalidate_calls_render_on_layout_after_current_frame():void { var layout:ITextLayout = nice(ITextLayout); stub(layout).method("render"); - + engine.layout = layout; engine.invalidate(); - + Async.handleEvent(this, delayTimer, TimerEvent.TIMER_COMPLETE, - handleInvalidateCallsRenderOnLayoutAfterCurrentFrame, 500, layout); - + handleInvalidateCallsRenderOnLayoutAfterCurrentFrame, 1500, layout); + delayTimer.start(); } - + private function handleInvalidateCallsRenderOnLayoutAfterCurrentFrame(event:Event, layout:ITextLayout):void { verify(layout).method("render").once(); } - + [Test(async)] public function invalidate_calls_render_on_decor_after_current_frame():void { var decor:ITextDecor = nice(ITextDecor); stub(decor).method("render"); - + engine.decor = decor; engine.invalidate(); - + Async.handleEvent(this, delayTimer, TimerEvent.TIMER_COMPLETE, - handleInvalidateCallsRenderOnDecorAfterCurrentFrame, 500, decor); - + handleInvalidateCallsRenderOnDecorAfterCurrentFrame, 1500, decor); + delayTimer.start(); } - + private function handleInvalidateCallsRenderOnDecorAfterCurrentFrame(event:Event, decor:ITextDecor):void { verify(decor).method("render").once(); } - + [Test(async)] public function invalidate_calls_resetShapes_on_layout_after_current_frame():void { var layout:ITextLayout = nice(ITextLayout); stub(layout).method("resetShapes"); - + engine.layout = layout; engine.invalidate(); - + Async.handleEvent(this, delayTimer, TimerEvent.TIMER_COMPLETE, - handleInvalidateCallsResetShapesOnLayoutAfterCurrentFrame, 500, layout); - + handleInvalidateCallsResetShapesOnLayoutAfterCurrentFrame, 1500, layout); + delayTimer.start(); } - + private function handleInvalidateCallsResetShapesOnLayoutAfterCurrentFrame(event:Event, layout:ITextLayout):void { verify(layout).method("resetShapes").once(); } - + //---------------------------------------------------- // rendering //---------------------------------------------------- - + [Test] public function render_lines_renders_layout():void { var layout:ITextLayout = nice(ITextLayout); stub(layout).method("render"); - + engine.layout = layout; - engine.renderLines(); - + engine.invalidateLines(); + engine.render(); + verify(layout).method("render").once(); } - - [Test] - public function render_lines_clears_layout():void - { - var layout:ITextLayout = nice(ITextLayout); - stub(layout).method("clear"); - - engine.layout = layout; - engine.renderLines(); - - verify(layout).method("clear").once(); - } - + [Test] public function render_decorations_resets_shapes_in_layout():void { var layout:ITextLayout = nice(ITextLayout); stub(layout).method("resetShapes"); - + engine.layout = layout; - engine.renderDecorations(); - + engine.invalidateDecorations(); + engine.render(); + verify(layout).method("resetShapes").once(); } - + [Test] public function render_decorations_renders_decor():void { var decor:ITextDecor = nice(ITextDecor); stub(decor).method("render"); - + engine.decor = decor; - engine.renderDecorations(); - + engine.invalidateDecorations(); + engine.render(); + verify(decor).method("render").once(); } + + private function renderDummyTextInEngine():void + { + var target:Sprite = new Sprite(); + engine.layout.addContainer(new TextContainerBase(target, 100)); + engine.blockFactory.data = "Let's test this shit."; + engine.invalidate(); + engine.render(); + } + + [Test] + public function selecting_text_calls_decorate():void + { + renderDummyTextInEngine(); + + var decor:ITextDecor = strict(ITextDecor); + stub(decor).method("decorate"); + stub(decor).method("undecorate"); + stub(decor).setter("engine"); + stub(decor).method("hasDecoration"); + + engine.decor = decor; + + engine.select(0, 1); + + verify(decor); + } } } diff --git a/test/org/tinytlf/extensions/fcss/FCSSTextStylerTest.as b/test/org/tinytlf/extensions/fcss/FCSSTextStylerTest.as new file mode 100755 index 0000000..da0c050 --- /dev/null +++ b/test/org/tinytlf/extensions/fcss/FCSSTextStylerTest.as @@ -0,0 +1,58 @@ +package org.tinytlf.extensions.fcss +{ + import flash.text.engine.ElementFormat; + + import org.flexunit.Assert; + import org.tinytlf.ITextEngine; + import org.tinytlf.TextEngine; + import org.tinytlf.styles.FCSSTextStyler; + + public class FCSSTextStylerTest + { + private var engine:ITextEngine; + private var styler:FCSSTextStyler; + private var css:XML = <_>; + + [Before] + public function setUp():void + { + engine = new TextEngine(); + styler = new FCSSTextStyler(); + engine.styler = styler; + styler.style = css.toString(); + } + + [After] + public function tearDown():void + { + engine = null; + styler = null; + } + + [Test] + public function dummy():void + { + } + } +} \ No newline at end of file diff --git a/test/org/tinytlf/extensions/xml/layout/factory/XMLBlockFactoryTests.as b/test/org/tinytlf/extensions/xml/layout/factory/XMLBlockFactoryTests.as new file mode 100755 index 0000000..4f73700 --- /dev/null +++ b/test/org/tinytlf/extensions/xml/layout/factory/XMLBlockFactoryTests.as @@ -0,0 +1,44 @@ +package org.tinytlf.extensions.xml.layout.factory +{ + import flash.text.engine.TextBlock; + + import org.flexunit.assertThat; + import org.tinytlf.ITextEngine; + import org.tinytlf.TextEngine; + + public class XMLBlockFactoryTests + { + private var engine:ITextEngine; + + [Before] + public function setUp():void + { + engine = new TextEngine(); + } + + [After] + public function tearDown():void + { + } + + [BeforeClass] + public static function setUpBeforeClass():void + { + } + + [AfterClass] + public static function tearDownAfterClass():void + { + } + + [Test] + public function paragraph_node_creates_one_text_block():void + { + trace('break here'); + engine.blockFactory.data = "

    Text

    "; + engine.prerender(); + var blocks:Vector. = engine.blockFactory.blocks; + assertThat(blocks.length == 1); + } + } +} \ No newline at end of file diff --git a/test/org/tinytlf/extensions/fcss/xhtml/styles/FCSSTextStylerTest.as b/test/org/tinytlf/extensions/xml/xhtml/fcss/styles/FCSSTextStylerTest.as old mode 100644 new mode 100755 similarity index 96% rename from test/org/tinytlf/extensions/fcss/xhtml/styles/FCSSTextStylerTest.as rename to test/org/tinytlf/extensions/xml/xhtml/fcss/styles/FCSSTextStylerTest.as index 85966aa..359f781 --- a/test/org/tinytlf/extensions/fcss/xhtml/styles/FCSSTextStylerTest.as +++ b/test/org/tinytlf/extensions/xml/xhtml/fcss/styles/FCSSTextStylerTest.as @@ -1,10 +1,11 @@ -package org.tinytlf.extensions.fcss.xhtml.styles +package org.tinytlf.extensions.xml.xhtml.fcss.styles { import flash.text.engine.ElementFormat; import org.flexunit.Assert; import org.tinytlf.ITextEngine; import org.tinytlf.TextEngine; + import org.tinytlf.extensions.styles.fcss.FCSSTextStyler; public class FCSSTextStylerTest { diff --git a/test/org/tinytlf/layout/TextContainerBaseTests.as b/test/org/tinytlf/layout/TextContainerBaseTests.as old mode 100644 new mode 100755 index e1cd833..538e429 --- a/test/org/tinytlf/layout/TextContainerBaseTests.as +++ b/test/org/tinytlf/layout/TextContainerBaseTests.as @@ -8,108 +8,108 @@ package org.tinytlf.layout { import flash.display.Sprite; import flash.events.Event; - + import mockolate.prepare; - + import mx.core.UIComponent; - + import org.flexunit.Assert; import org.flexunit.async.Async; import org.fluint.uiImpersonation.UIImpersonator; import org.hamcrest.assertThat; import org.hamcrest.object.*; import org.tinytlf.ITextEngine; - + public class TextContainerBaseTests { private var container:TextContainerBase; private var target:UIComponent; - + [Before(async, ui)] public function setup():void { target = new UIComponent(); container = new TextContainerBase(target); - + UIImpersonator.addChild(target); - + Async.proceedOnEvent(this, - prepare(ITextEngine), - Event.COMPLETE); + prepare(ITextEngine), + Event.COMPLETE); } - + [After(ui)] public function tearDown():void { UIImpersonator.removeAllChildren(); container = null; } - + [Test] public function was_constructed():void { assertThat(container, notNullValue()); } - + [Test] public function has_target_after_construction():void { assertThat(container.target, strictlyEqualTo(target)); } - + [Test] public function target_was_set():void { var newTarget:UIComponent = new UIComponent(); - + container.target = newTarget; - + assertThat(container.target, strictlyEqualTo(newTarget)); } - + [Test] - public function child_added_to_target_after_setting_targer():void + public function child_added_to_target_after_setting_target():void { - assertThat(target.numChildren, equalTo(1)); + assertThat(target.numChildren, equalTo(2)); } - - [Test] - public function shapes_is_target_child_after_setting_target():void - { - assertThat(container.shapes, strictlyEqualTo(target.getChildAt(0))); - } - - [Test] - public function children_moved_to_new_shapes_when_set():void - { - var shapes:Sprite = new Sprite(); - - shapes.addChild(new Sprite()); - container.shapes = shapes; - - assertThat(shapes.numChildren, equalTo(1)); - } - - [Test] - public function children_removed_from_existing_shapes_when_shapes_set():void - { - var targetShapes:Sprite = container.shapes; - targetShapes.addChild(new Sprite()); - container.shapes = new Sprite(); - - assertThat(targetShapes.numChildren, equalTo(0)); - } - + +// [Test] +// public function shapes_is_target_child_after_setting_target():void +// { +// assertThat(container.shapes, strictlyEqualTo(target.getChildAt(0))); +// } +// +// [Test] +// public function children_moved_to_new_shapes_when_set():void +// { +// var shapes:Sprite = new Sprite(); +// +// shapes.addChild(new Sprite()); +// container.shapes = shapes; +// +// assertThat(shapes.numChildren, equalTo(1)); +// } +// +// [Test] +// public function children_removed_from_existing_shapes_when_shapes_set():void +// { +// var targetShapes:Sprite = container.shapes; +// targetShapes.addChild(new Sprite()); +// container.shapes = new Sprite(); +// +// assertThat(targetShapes.numChildren, equalTo(0)); +// } + [Test] public function allowed_width_default_is_NaN():void { - Assert.assertTrue(isNaN(container.allowedWidth)); + Assert.assertTrue(isNaN(container.explicitWidth)); } - + [Test] public function allowed_height_default_is_NaN():void { - Assert.assertTrue(isNaN(container.allowedHeight)); + Assert.assertTrue(isNaN(container.explicitHeight)); } } } \ No newline at end of file diff --git a/test/org/tinytlf/layout/TextLayoutBaseTests.as b/test/org/tinytlf/layout/TextLayoutBaseTests.as old mode 100644 new mode 100755 index 8391b37..4a12d83 --- a/test/org/tinytlf/layout/TextLayoutBaseTests.as +++ b/test/org/tinytlf/layout/TextLayoutBaseTests.as @@ -7,237 +7,222 @@ package org.tinytlf.layout { import flash.events.Event; - import flash.text.engine.*; - + import mockolate.*; - + import org.flexunit.async.Async; import org.hamcrest.assertThat; import org.hamcrest.object.*; import org.tinytlf.ITextEngine; - + public class TextLayoutBaseTests { private var layout:TextLayoutBase; - + [Before(async)] public function setup():void { layout = new TextLayoutBase(); Async.proceedOnEvent(this, - prepare(ITextEngine, ITextContainer), - Event.COMPLETE); + prepare(ITextEngine, ITextContainer), + Event.COMPLETE); } - + [After] public function tearDown():void { layout = null; } - + //---------------------------------------------------- // initialization //---------------------------------------------------- - + [Test] public function text_layout_was_constructed():void { assertThat(layout, notNullValue()); } - + [Test] public function engine_was_set():void { var engine:ITextEngine = nice(ITextEngine); - + layout.engine = engine; - + assertThat(layout.engine, strictlyEqualTo(engine)); } - + [Test] public function containers_initialized_to_empty_vector():void { assertThat(layout.containers.length, equalTo(0)); } - + //---------------------------------------------------- // basic public methods //---------------------------------------------------- - + [Test] public function container_was_added():void { var container:ITextContainer = stubBasicTextContainer(); - + layout.addContainer(container); - + assertThat(layout.containers.length, equalTo(1)); } - + [Test] public function container_was_removed():void { var container:ITextContainer = stubBasicTextContainer(); - + layout.addContainer(container); layout.removeContainer(container); - + assertThat(layout.containers.length, equalTo(0)); } - + [Test] public function container_was_retrieved_for_line():void { var block:TextBlock = createTextBlockWithSmallContent(); var line:TextLine = block.createTextLine(); var retrievedContainer:ITextContainer; - + var container:ITextContainer = stubBasicTextContainer(); stub(container).method("hasLine").args(line).returns(true); - + layout.addContainer(container); retrievedContainer = layout.getContainerForLine(line); - + assertThat(retrievedContainer, strictlyEqualTo(container)); } - - [Test] - public function clear_calls_clear_on_containers():void - { - var container:ITextContainer = stubBasicTextContainer(); - - layout.addContainer(container); - layout.clear(); - - verify(container).method("clear").once(); - } - + [Test] public function reset_shapes_calls_reset_shapes_on_container():void { var container:ITextContainer = stubBasicTextContainer(); - + layout.addContainer(container); layout.resetShapes(); - + verify(container).method("resetShapes").once(); } - + //---------------------------------------------------- // render //---------------------------------------------------- - + [Test] public function render_laid_out_single_block_in_single_container():void { var blocks:Vector. = new Vector.; var block:TextBlock = new TextBlock(); - + var container:ITextContainer = nice(ITextContainer); stub(container).method("layout").args(block, instanceOf(TextLine)); - + layout.addContainer(container); - + blocks.push(block); - - layout.render(blocks); - + + layout.render(); + verify(container).method("layout").once(); } - + [Test] public function render_laid_out_single_block_in_multiple_container():void { var blocks:Vector. = new Vector.; var block:TextBlock = createTextBlockWithSmallContent(); - var line:TextLine = block.createTextLine(); - + var line:TextLine = block.createTextLine(null, 0, 0, true); + var container:ITextContainer = nice(ITextContainer); - stub(container).method("layout").args(block, null).returns(line); - + stub(container).method("layout").args(instanceOf(TextBlock), instanceOf(TextLine)).returns(line); + var container2:ITextContainer = nice(ITextContainer); - stub(container2).method("layout").args(block, line); - + stub(container2).method("layout").args(instanceOf(TextBlock), instanceOf(TextLine)); + blocks.push(block); - + layout.addContainer(container); layout.addContainer(container2); - - layout.render(blocks); - - + + layout.render(); + verify(container2).method("layout").once(); } - + [Test] public function render_laid_out_multiple_blocks_in_single_container():void { var blocks:Vector. = new Vector.; var block:TextBlock = createTextBlockWithSmallContent(); var block2:TextBlock = createTextBlockWithSmallContent(); - + var container:ITextContainer = nice(ITextContainer); stub(container).method("layout").args(instanceOf(TextBlock), null); - + blocks.push(block); blocks.push(block2); - + layout.addContainer(container); - - layout.render(blocks); -1 - + + layout.render(); + verify(container).method("layout").args(block, null).once(); verify(container).method("layout").args(block2, null).once(); } - + [Test] public function render_laid_out_multiple_blocks_in_multiple_containers():void { var blocks:Vector. = new Vector.; var block:TextBlock = createTextBlockWithSmallContent(); var block2:TextBlock = createTextBlockWithSmallContent(); - var line:TextLine = block.createTextLine(); - + var line:TextLine = block.createTextLine(null, 0, 0, true); + var container:ITextContainer = nice(ITextContainer); - stub(container).method("layout").args(instanceOf(TextBlock), null).returns(line); - + stub(container).method("layout").args(instanceOf(TextBlock), instanceOf(TextLine)).returns(line); + var container2:ITextContainer = nice(ITextContainer); - stub(container2).method("layout").args(instanceOf(TextBlock), null); - + stub(container2).method("layout").args(instanceOf(TextBlock), instanceOf(TextLine)); + blocks.push(block); blocks.push(block2); - + layout.addContainer(container); layout.addContainer(container2); - - layout.render(blocks); - - - verify(container).method("layout").args(block, null).once(); - + + layout.render(); + + verify(container).method("layout").args(block, line).once(); + verify(container2).method("layout").args(block, line).once(); verify(container2).method("layout").args(block2, null).once(); } - + //---------------------------------------------------- // // test helper methods // //---------------------------------------------------- - + private function stubBasicTextContainer():ITextContainer { var container:ITextContainer = nice(ITextContainer); stub(container).method("clear"); stub(container).method("resetShapes"); stub(container).setter("engine"); - + return container; } - + private function createTextBlockWithSmallContent():TextBlock { var block:TextBlock = new TextBlock(); diff --git a/test/org/tinytlf/styles/StyleAwareActorTests.as b/test/org/tinytlf/styles/StyleAwareActorTests.as new file mode 100755 index 0000000..152a1bf --- /dev/null +++ b/test/org/tinytlf/styles/StyleAwareActorTests.as @@ -0,0 +1,6 @@ +package org.tinytlf.styles +{ + public class StyleAwareActorTests + { + } +} \ No newline at end of file diff --git a/test/org/tinytlf/suites/TinyTLFTestSuite.as b/test/org/tinytlf/suites/TinyTLFTestSuite.as old mode 100644 new mode 100755 index 123accd..6b24e22 --- a/test/org/tinytlf/suites/TinyTLFTestSuite.as +++ b/test/org/tinytlf/suites/TinyTLFTestSuite.as @@ -1,7 +1,7 @@ package org.tinytlf.suites { import org.tinytlf.*; - import org.tinytlf.extensions.fcss.xhtml.styles.FCSSTextStylerTest; + import org.tinytlf.extensions.fcss.FCSSTextStylerTest; import org.tinytlf.layout.TextContainerBaseTests; import org.tinytlf.layout.TextLayoutBaseTests; diff --git a/test/TinyTLFTest.mxml b/test/tinytlfTests.mxml old mode 100644 new mode 100755 similarity index 100% rename from test/TinyTLFTest.mxml rename to test/tinytlfTests.mxml diff --git a/tinytlf-components/src/org/tinytlf/components/ComponentBase.as b/tinytlf-components/src/org/tinytlf/components/ComponentBase.as new file mode 100755 index 0000000..c8f60df --- /dev/null +++ b/tinytlf-components/src/org/tinytlf/components/ComponentBase.as @@ -0,0 +1,59 @@ +package org.tinytlf.components +{ + import flash.display.Sprite; + + [Exclude(name="$width", kind="property")] + [Exclude(name="$height", kind="property")] + + public class ComponentBase extends Sprite + { + public function ComponentBase() + { + super(); + } + + private var _width:Number = 0; + + override public function get width():Number + { + return _width; + } + + override public function set width(value:Number):void + { + _width = value; + } + + public function get $width():Number + { + return super.width; + } + + public function set $width(value:Number):void + { + super.width = value; + } + + private var _height:Number = 0; + + override public function get height():Number + { + return _height; + } + + override public function set height(value:Number):void + { + _height = value; + } + + public function get $height():Number + { + return super.height; + } + + public function set $height(value:Number):void + { + super.height = value; + } + } +} \ No newline at end of file diff --git a/tinytlf-components/src/org/tinytlf/components/TextColumnContainer.as b/tinytlf-components/src/org/tinytlf/components/TextColumnContainer.as new file mode 100755 index 0000000..ffe9945 --- /dev/null +++ b/tinytlf-components/src/org/tinytlf/components/TextColumnContainer.as @@ -0,0 +1,281 @@ +package org.tinytlf.components +{ + import flash.display.*; + import flash.events.*; + import flash.text.engine.*; + + import org.tinytlf.ITextEngine; + import org.tinytlf.layout.*; + import org.tinytlf.layout.constraints.*; + import org.tinytlf.layout.orientation.*; + + [Event(name="initScrollBar", type="flash.events.Event")] + + /** + * TextColumnContainer is a Sprite which conveniently implements + * ITextContainer, and composites in a TextContainerBase for the + * implementation. Since it's a Sprite, it's easy to use in component style + * layouts without also having to manage and update an external + * ITextContainer. + */ + public class TextColumnContainer extends Sprite implements IConstraintTextContainer + { + public function TextColumnContainer() + { + super(); + + focusRect = false; + + container = new ConstraintTextContainer(this, 100); + } + + private var _height:Number = 0; + + override public function get height():Number + { + return _height; + } + + override public function set height(value:Number):void + { + if(height === value) + return; + + _height = Math.max(value, 1); + explicitHeight = value; + } + + private var _width:Number = 0; + + override public function get width():Number + { + return _width; + } + + override public function set width(value:Number):void + { + if(width === value) + return; + + _width = Math.max(value, 1); + explicitWidth = _width; + } + + private var container:IConstraintTextContainer; + + public function get engine():ITextEngine + { + return container.engine; + } + + public function set engine(textEngine:ITextEngine):void + { + container.engine = textEngine; + } + + public function get target():Sprite + { + return this; + } + + public function set target(textContainer:Sprite):void + { + //do nothing + } + + public function get background():Sprite + { + return container.background; + } + + public function set background(shapesContainer:Sprite):void + { + container.background = shapesContainer; + } + + public function get foreground():Sprite + { + return container.foreground; + } + + public function set foreground(shapesContainer:Sprite):void + { + container.foreground = shapesContainer; + } + + public function get lines():Sprite + { + return container.lines; + } + + public function set lines(linesContainer:Sprite):void + { + container.lines = linesContainer; + } + + public function get explicitWidth():Number + { + return container.explicitWidth; + } + + public function set explicitWidth(value:Number):void + { + container.explicitWidth = value; + } + + public function get explicitHeight():Number + { + return container.explicitHeight; + } + + public function set explicitHeight(value:Number):void + { + container.explicitHeight = value; + } + + public function get measuredWidth():Number + { + return container.measuredWidth; + } + + public function set measuredWidth(value:Number):void + { + container.measuredWidth = value; + } + + public function get measuredHeight():Number + { + return container.measuredHeight; + } + + public function set measuredHeight(value:Number):void + { + container.measuredHeight = value; + } + + public function get totalWidth():Number + { + return container.totalWidth; + } + + public function set totalWidth(value:Number):void + { + container.totalWidth = value; + } + + public function get totalHeight():Number + { + return container.totalHeight; + } + + public function set totalHeight(value:Number):void + { + container.totalHeight = value; + } + + public function get scrollable():Boolean + { + return container.scrollable; + } + + public function set scrollable(value:Boolean):void + { + container.scrollable = value; + } + + public function resetShapes():void + { + container.resetShapes(); + } + + public function preLayout():void + { + container.preLayout(); + } + + public function postLayout():void + { + container.postLayout(); + + if(scrollable && measuredHeight > explicitHeight) + { + dispatchEvent(new Event('initScrollBar', true, true)); + } + + drawBackground(); + } + + public function layout(block:TextBlock, line:TextLine):TextLine + { + return container.layout(block, line); + } + + public function hasLine(line:TextLine):Boolean + { + return container.hasLine(line); + } + + public function get majorDirection():IMajorOrientation + { + return container.majorDirection; + } + + public function set majorDirection(delegate:IMajorOrientation):void + { + container.majorDirection = delegate; + } + + public function get minorDirection():IMinorOrientation + { + return container.minorDirection; + } + + public function set minorDirection(delegate:IMinorOrientation):void + { + container.minorDirection = delegate; + } + + public function set constraintFactory(factory:IConstraintFactory):void + { + container.constraintFactory = factory; + } + + public function get constraintFactory():IConstraintFactory + { + return container.constraintFactory; + } + + public function get constraints():Vector. + { + return container.constraints; + } + + public function addConstraint(constraint:ITextConstraint):void + { + container.addConstraint(constraint); + } + + public function getConstraint(element:*):ITextConstraint + { + return container.getConstraint(element); + } + + public function removeConstraint(constraint:ITextConstraint):void + { + container.removeConstraint(constraint); + } + + private function drawBackground():void + { + return; + + var g:Graphics = graphics; + g.clear(); + g.lineStyle(); + g.beginFill(0x00, 0); + g.drawRect(0, 0, + explicitWidth || measuredWidth || width, + explicitHeight || measuredHeight || height); + } + } +} \ No newline at end of file diff --git a/tinytlf-components/src/org/tinytlf/components/TextField.as b/tinytlf-components/src/org/tinytlf/components/TextField.as new file mode 100755 index 0000000..31870aa --- /dev/null +++ b/tinytlf-components/src/org/tinytlf/components/TextField.as @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.components +{ + import com.bit101.components.VScrollBar; + + import flash.display.*; + import flash.events.*; + import flash.geom.*; + import flash.utils.*; + + import org.tinytlf.*; + import org.tinytlf.layout.*; + import org.tinytlf.layout.constraints.*; + import org.tinytlf.styles.*; + + public class TextField extends ComponentBase implements IStyleAware + { + public function TextField() + { + super(); + + engine.layout.addContainer(ITextContainer(addChild(container = new TextColumnContainer()))); + container.addEventListener('initScrollBar', onInitScrollbar); + + width = 100; + configuration = new TextFieldEngineConfiguration(); + layoutConstraintFactory = new HTMLConstraintFactory(); + } + + protected var container:TextColumnContainer; + + override public function set height(value:Number):void + { + if(height === value) + return; + + super.height = value; + container.height = value; + scrollPosition = scrollPosition; + } + + override public function set width(value:Number):void + { + if(width === value) + return; + + super.width = value; + + if(scrollBar) + { + engine.scrollPosition = 0; + scrollBar.value = 0; + value = value - scrollBar.width - 5; + } + + container.width = value; + scrollPosition = scrollPosition; + } + + protected var _configuration:ITextEngineConfiguration; + public function set configuration(engineConfiguration:ITextEngineConfiguration):void + { + if(engineConfiguration === _configuration) + return; + + //Save this configuration so we can apply it if the engine changes + _configuration = engineConfiguration; + + engine.configuration = _configuration; + engine.invalidate(); + } + + private var _editable:Boolean = false; + public function get editable():Boolean + { + return _editable; + } + + public function set editable(value:Boolean):void + { + if(value === editable) + return; + + _editable = value; + configuration = new TextFieldEngineConfiguration(selectable, editable); + } + + private var _engine:ITextEngine; + + public function get engine():ITextEngine + { + if(!_engine) + { + _engine = new TextFieldEngine(this, stage); + + if(!stage) + { + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + } + + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if(textEngine == _engine) + return; + + _engine = textEngine; + + //If we've previously set an engine configuration, re-apply it now. + if(_configuration) + { + _engine.configuration = _configuration; + } + } + + public function get scrollable():Boolean + { + return container.scrollable; + } + + public function set scrollable(value:Boolean):void + { + container.scrollable = value; + } + + protected var scrollP:Number = 0; + + public function get scrollPosition():Number + { + return scrollP; + } + + public function set scrollPosition(value:Number):void + { + if(value != scrollP) + { + if(!scrollBar) + return; + scrollP = Math.min(Math.max(value, 0), scrollBar.maximum); + engine.scrollPosition = scrollP; + scrollBar.value = scrollP; + } + + if(width && height) + container.scrollRect = new Rectangle(-5, scrollP, width + 5, height); + } + + private var _selectable:Boolean = true; + public function get selectable():Boolean + { + return _selectable; + } + + public function set selectable(value:Boolean):void + { + if(value === _selectable) + return; + + _selectable = value; + configuration = new TextFieldEngineConfiguration(selectable, editable); + } + + private var _text:String = ""; + public function get text():String + { + return _text; + } + + public function set text(value:String):void + { + if(_text === value) + return; + + _text = value; + engine.blockFactory.data = _text; + engine.invalidate(); + } + + public function get layoutConstraintFactory():IConstraintFactory + { + return container.constraintFactory; + } + + public function set layoutConstraintFactory(factory:IConstraintFactory):void + { + container.constraintFactory = factory; + engine.invalidate(); + } + + public function get style():Object + { + return engine.styler.style; + } + + public function set style(value:Object):void + { + engine.styler.style = value; + } + + public function clearStyle(styleProp:String):Boolean + { + return engine.styler.clearStyle(styleProp); + } + + public function getStyle(styleProp:String):* + { + return engine.styler.getStyle(styleProp); + } + + public function setStyle(styleProp:String, newValue:*):void + { + engine.styler.setStyle(styleProp, newValue); + } + + public function mergeWith(object:Object):void + { + engine.styler.mergeWith(object); + } + + public function unmergeWith(object:Object):void + { + engine.styler.unmergeWith(object); + } + + public function applyTo(object:Object):void + { + engine.styler.applyTo(object); + } + + public function unapplyTo(object:Object):void + { + engine.styler.unapplyTo(object); + } + + private function onAddedToStage(event:Event):void + { + if(event.eventPhase != EventPhase.AT_TARGET) + return; + + removeEventListener(event.type, onAddedToStage); + engine.stage = stage; + + initialize(); + } + + public function initialize():void + { + } + + private function onInitScrollbar(event:Event):void + { + event.stopPropagation(); + initScrollBar(); + } + + private var scrollBar:VScrollBar; + + protected function initScrollBar():void + { + if(!scrollBar) + { + scrollBar = new VScrollBar(this, 0, 0, onScrollChange); + addChild(scrollBar); + scrollBar.lineSize = 5; + scrollBar.pageSize = 15; + scrollBar.height = height; + scrollBar.y = 0; + scrollBar.minimum = 0; + + container.width = width - scrollBar.width - 10; + container.scrollRect = new Rectangle(-5, 0, width + 5, height); + + setTimeout(engine.invalidate, 10); + } + + var totalHeight:Number = container.totalHeight; + + scrollBar.x = width - scrollBar.width; + scrollBar.maximum = totalHeight - height; + scrollBar.setThumbPercent(height / totalHeight); + } + + protected function onScrollChange(event:Event = null):void + { + if(!scrollBar) + return; + + scrollPosition = scrollBar.value; + } + } +} +import flash.display.Stage; + +import org.tinytlf.TextEngine; +import org.tinytlf.components.TextField; + +internal class TextFieldEngine extends TextEngine +{ + public function TextFieldEngine(textField:TextField, stage:Stage) + { + tf = textField; + + super(stage); + } + private var tf:TextField; + + override public function get scrollPosition():Number + { + return tf.scrollPosition; + } + + override public function set scrollPosition(value:Number):void + { + if(value === tf.scrollPosition) + { + super.scrollPosition = value; + return; + } + + tf.scrollPosition = value; + } +} diff --git a/tinytlf-components/src/org/tinytlf/components/TextFieldEngineConfiguration.as b/tinytlf-components/src/org/tinytlf/components/TextFieldEngineConfiguration.as new file mode 100755 index 0000000..b968139 --- /dev/null +++ b/tinytlf-components/src/org/tinytlf/components/TextFieldEngineConfiguration.as @@ -0,0 +1,163 @@ +package org.tinytlf.components +{ + import org.tinytlf.*; + import org.tinytlf.behaviors.*; + import org.tinytlf.conversion.*; + import org.tinytlf.decor.*; + import org.tinytlf.decor.selection.*; + import org.tinytlf.gestures.*; + import org.tinytlf.interaction.*; + import org.tinytlf.layout.ITextContainer; + import org.tinytlf.styles.*; + + public class TextFieldEngineConfiguration implements ITextEngineConfiguration + { + public function TextFieldEngineConfiguration(selectable:Boolean = true, editable:Boolean = false) + { + this.selectable = selectable; + this.editable = editable; + } + + public var selectable:Boolean = true; + public var editable:Boolean = false; + + public function configure(engine:ITextEngine):void + { + engine.interactor = new CascadingTextInteractor(); + engine.styler = new FCSSTextStyler(); + engine.blockFactory = new XMLEditableBlockFactory(); + + mapDecorations(engine); + mapEventMirrors(engine); + mapGestures(engine); + applyGestures(engine); + mapElementAdapters(engine); + mapStyles(engine); + } + + protected function mapDecorations(engine:ITextEngine):void + { + var decor:ITextDecor = engine.decor; + + if (!decor.hasDecoration("backgroundColor")) + decor.mapDecoration("backgroundColor", BackgroundColorDecoration); + + if (!decor.hasDecoration("bullet")) + decor.mapDecoration("bullet", BulletDecoration); + + if (!decor.hasDecoration("horizontalRule")) + decor.mapDecoration("horizontalRule", HorizontalRuleDecoration); + + if (!decor.hasDecoration("selection")) + decor.mapDecoration("selection", StandardSelectionDecoration); + + if (!decor.hasDecoration("underline")) + decor.mapDecoration("underline", UnderlineDecoration); + + if (!decor.hasDecoration("strikethrough")) + decor.mapDecoration("strikethrough", StrikeThroughDecoration); + + if (!decor.hasDecoration("caret")) + decor.mapDecoration("caret", CaretDecoration); + + if (!decor.hasDecoration("popup")) + decor.mapDecoration("popup", PopupDecoration); + + if (!decor.hasDecoration("border")) + decor.mapDecoration("border", BorderDecoration); + + if (!selectable) + decor.unMapDecoration("selection"); + + if (!editable) + decor.unMapDecoration("caret"); + } + + protected function mapEventMirrors(engine:ITextEngine):void + { + if (!engine.interactor.hasMirror("a")) + engine.interactor.mapMirror("a", AnchorMirror); + } + + protected function mapGestures(engine:ITextEngine):void + { + var interactor:IGestureInteractor = IGestureInteractor(engine.interactor); + + interactor.removeAllGestures(); + + var focus:FocusBehavior = new FocusBehavior(); + var mouseClick:MouseClickGesture = new MouseClickGesture(); + interactor.addGesture(mouseClick, focus); + + if (selectable) + { + var iBeam:IBeamBehavior = new IBeamBehavior(); + var scroll:ScrollBehavior = new ScrollBehavior(); + var mouseCharSelect:CharacterSelectionBehavior = new CharacterSelectionBehavior(); + var mouseWordSelect:WordSelectionBehavior = new WordSelectionBehavior(); + var paragraphSelect:ParagraphSelectionBehavior = new ParagraphSelectionBehavior(); + + var mouseOver:MouseOverGesture = new MouseOverGesture(); + var mouseOut:MouseOutGesture = new MouseOutGesture(); + var mouseDoubleDown:MouseDoubleDownGesture = new MouseDoubleDownGesture(); + var mouseTripleDown:MouseTripleDownGesture = new MouseTripleDownGesture(); + var mouseWheel:MouseWheelGesture = new MouseWheelGesture(); + + interactor.addGesture(mouseOver, iBeam); + interactor.addGesture(mouseOut, iBeam); + interactor.addGesture(mouseClick, mouseCharSelect, scroll); + interactor.addGesture(mouseDoubleDown, mouseWordSelect, scroll); + interactor.addGesture(mouseTripleDown, paragraphSelect, scroll); + interactor.addGesture(mouseWheel, scroll); + + if (editable) + { + mouseTripleDown.removeBehavior(paragraphSelect); + mouseTripleDown.addBehavior(new LineSelectionBehavior()); + } + } + } + + protected function applyGestures(engine:ITextEngine):void + { +// var containers:Vector. = engine.layout.containers; +// containers.forEach(function(container:ITextContainer, ...args):void{ +// engine.interactor.getMirror(container); +// }); + } + + protected function mapElementAdapters(engine:ITextEngine):void + { + var factory:ITextBlockFactory = engine.blockFactory; + + if (!factory.hasElementFactory('ul')) + factory.mapElementFactory('ul', HTMLListAdapter); + + if (!factory.hasElementFactory('li')) + factory.mapElementFactory('li', HTMLListItemAdapter); + + if (!factory.hasElementFactory('br')) + factory.mapElementFactory('br', HTMLLineBreakAdapter); + + if (!factory.hasElementFactory('colbr')) + factory.mapElementFactory('colbr', HTMLColumnBreakAdapter); + + if (!factory.hasElementFactory('img')) + factory.mapElementFactory('img', HTMLImageAdapter); + + if (!factory.hasElementFactory('hr')) + factory.mapElementFactory('hr', HTMLHorizontalRuleAdapter); + } + + protected function mapStyles(engine:ITextEngine):void + { + var styler:ITextStyler = engine.styler; + + if(!styler.getStyle('selectionColor')) + styler.setStyle('selectionColor', 0x0068FC); + if(!styler.getStyle('selectionAlpha')) + styler.setStyle('selectionAlpha', 0.28); + } + } +} + diff --git a/tinytlf-components/src/org/tinytlf/components/cs4/FlashTextField.as b/tinytlf-components/src/org/tinytlf/components/cs4/FlashTextField.as new file mode 100755 index 0000000..2084b6a --- /dev/null +++ b/tinytlf-components/src/org/tinytlf/components/cs4/FlashTextField.as @@ -0,0 +1,41 @@ +package org.tinytlf.components.cs4 +{ + import flash.geom.Transform; + + import org.tinytlf.components.TextField; + + public class FlashTextField extends TextField + { + public function FlashTextField() + { + super(); + +// if($width > 0) +// { +// width = $width; +// $width = 100; +// } +// if($height > 0) +// { +// height = $height; +// $height = 100; +// } + + removeChildAt(0); + graphics.clear(); + } + + public function setSize(w:Number, h:Number):void + { + width = w; + height = h; + draw(); + } + + public function draw():void + { + engine.invalidate(); + engine.render(); + } + } +} diff --git a/tinytlf-components/src/org/tinytlf/components/cs4/TinytlfTextField.fla b/tinytlf-components/src/org/tinytlf/components/cs4/TinytlfTextField.fla new file mode 100755 index 0000000..773b5b2 Binary files /dev/null and b/tinytlf-components/src/org/tinytlf/components/cs4/TinytlfTextField.fla differ diff --git a/tinytlf-components/src/org/tinytlf/components/spark/RichTextField.as b/tinytlf-components/src/org/tinytlf/components/spark/RichTextField.as new file mode 100755 index 0000000..9928e89 --- /dev/null +++ b/tinytlf-components/src/org/tinytlf/components/spark/RichTextField.as @@ -0,0 +1,212 @@ +package org.tinytlf.components.spark +{ + import flash.events.Event; + import flash.events.EventPhase; + + import org.tinytlf.ITextEngine; + import org.tinytlf.ITextEngineConfiguration; + import org.tinytlf.TextEngine; + import org.tinytlf.components.TextColumnContainer; + import org.tinytlf.components.TextFieldEngineConfiguration; + import org.tinytlf.layout.ITextContainer; + import org.tinytlf.styles.IStyleAware; + + import spark.components.supportClasses.GroupBase; + + public class RichTextField extends GroupBase + /* implements IStyleAware */ //can't do this because Flex's clearStyle is retarded. + { + public function RichTextField() + { + super(); + + engine.layout.addContainer(ITextContainer(addChild(container = new TextColumnContainer()))); + + measuredWidth = 100; + configuration = new TextFieldEngineConfiguration(true, false); + } + + override public function set verticalScrollPosition(value:Number):void + { + super.verticalScrollPosition = value; + engine.scrollPosition = value; + } + + protected var _configuration:ITextEngineConfiguration; + public function set configuration(engineConfiguration:ITextEngineConfiguration):void + { + if(engineConfiguration === _configuration) + return; + + //Save this configuration so we can apply it if the engine changes + _configuration = engineConfiguration; + + engine.configuration = _configuration; + engine.invalidate(); + invalidateDisplayList(); + } + + private var container:TextColumnContainer; + + private var _engine:ITextEngine; + + public function get engine():ITextEngine + { + if(!_engine) + { + _engine = new RichTextFieldEngine(this, stage); + + if(!stage) + { + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + } + + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if(textEngine == _engine) + return; + + _engine = textEngine; + + //If we've previously set an engine configuration, re-apply it now. + if(_configuration) + { + _engine.configuration = _configuration; + } + } + + private var _selectable:Boolean = true; + public function get selectable():Boolean + { + return _selectable; + } + + public function set selectable(value:Boolean):void + { + if(value === _selectable) + return; + + _selectable = value; + configuration = new TextFieldEngineConfiguration(selectable, false); + } + + private var _text:String = ""; + public function get text():String + { + return _text; + } + + public function set text(value:String):void + { + if(_text === value) + return; + + _text = value; + engine.blockFactory.data = _text; + engine.invalidate(); + invalidateDisplayList(); + } + + private function onAddedToStage(event:Event):void + { + if(event.eventPhase != EventPhase.AT_TARGET) + return; + + removeEventListener(event.type, onAddedToStage); + engine.stage = stage; + } + + override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void + { + super.updateDisplayList(unscaledWidth, unscaledHeight); + + container.width = unscaledWidth; + container.height = unscaledHeight; + + engine.invalidate(); + engine.render(); + + setContentSize(unscaledWidth, container.totalHeight); + } + + override public function get contentHeight():Number + { + return super.contentHeight; + } + + public function get style():Object + { + return engine.styler.style; + } + + public function set style(value:Object):void + { + engine.styler.style = value; + } + + override public function getStyle(styleProp:String):* + { + return engine.styler.getStyle(styleProp); + } + + override public function setStyle(styleProp:String, newValue:*):void + { + super.setStyle(styleProp, newValue); + + engine.styler.setStyle(styleProp, newValue); + } + + public function merge(merge:Object):void + { + engine.styler.mergeWith(merge); + } + + public function applyTo(object:Object):void + { + engine.styler.applyTo(object); + } + + override public function stylesInitialized():void + { + super.stylesInitialized(); + + merge(this); + } + } +} + +import flash.display.Stage; + +import org.tinytlf.TextEngine; +import org.tinytlf.components.spark.RichTextField; + +internal class RichTextFieldEngine extends TextEngine +{ + public function RichTextFieldEngine(textField:RichTextField, stage:Stage) + { + tf = textField; + + super(stage); + } + private var tf:RichTextField; + + override public function get scrollPosition():Number + { + return tf.verticalScrollPosition; + } + + override public function set scrollPosition(value:Number):void + { + if(value === tf.verticalScrollPosition) + { + super.scrollPosition = value; + return; + } + + tf.verticalScrollPosition = value; + } +} diff --git a/tinytlf-core/src/org/tinytlf/ITextEngine.as b/tinytlf-core/src/org/tinytlf/ITextEngine.as new file mode 100755 index 0000000..d6c4e30 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/ITextEngine.as @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf +{ + import flash.display.Stage; + import flash.geom.Point; + + import org.tinytlf.conversion.ITextBlockFactory; + import org.tinytlf.decor.ITextDecor; + import org.tinytlf.interaction.ITextInteractor; + import org.tinytlf.layout.ITextLayout; + import org.tinytlf.styles.ITextStyler; + + /** + *

    The ITextEngine is the facade which unifies the + * subsystems of tinytlf. Since tinytlf is relies on external definitions + * for implementation, the engine provides access to the decor, + * interactor, styler, and layout. + * ITextEngine supports external configuration through the + * ITextEngineConfiguration interface. To apply a + * configuration, write a class that implements + * ITextEngineConfiguration and pass an instance into the + * configuration setter of ITextEngine.

    + *

    The ITextEngine is also responsible for invalidation, + * selection, and the TextBlocks that make up a tinytlf text + * field.

    + * + * @see org.tinytlf.decor.ITextDecor + * @see org.tinytlf.interaction.ITextInteractor + * @see org.tinytlf.layout.ITextLayout + * @see org.tinytlf.styles.ITextStyler + * @see org.tinytlf.ITextEngineConfiguration + */ + public interface ITextEngine + { + /** + * The ITextBlockFactory for tinytlf. This object accepts arbitrary data + * and converts it into Flash Text Engine ContentElements and TextBlocks. + */ + function get blockFactory():ITextBlockFactory; + function set blockFactory(value:ITextBlockFactory):void; + + /** + * The index of the selection caret for the engine. + */ + function get caretIndex():int; + function set caretIndex(index:int):void; + + /** + * A setter which applies an ITextEngineConfiguration for + * this engine. ITextEngineConfiguration is meant to + * externally map all the properties for this engine's member maps + * (decoration renderers and properties on ITextDecor, + * event mirrors, gestures, and behaviors on ITextInteractor, + * IContentElementAdapters for elements parsed by + * ITextLayout's ITextBlockFactory member, and + * style definitions for the ITextStyler. + */ + function set configuration(engineConfiguration:ITextEngineConfiguration):void; + + /** + * The ITextDecor instance for the engine. + * + * @see org.tinytlf.decor.ITextDecor + */ + function get decor():ITextDecor; + function set decor(textDecor:ITextDecor):void; + + /** + * The ITextInteractor instance for the engine. + * + * @see org.tinytlf.interaction.ITextInteractor + */ + function get interactor():ITextInteractor; + function set interactor(textInteractor:ITextInteractor):void; + + /** + * The ITextLayout instance for the engine. + * + * @see org.tinytlf.layout.ITextLayout + */ + function get layout():ITextLayout; + function set layout(textlayout:ITextLayout):void; + + /** + * The scroll position of the engine. This value is in pixels. Defined + * centrally here, but usually only needed during layout. + */ + function get scrollPosition():Number; + function set scrollPosition(value:Number):void; + + /** + * A point whose x property represents the engine's + * selection startIndex and y property represents + * the engine's selection endIndex. + */ + function get selection():Point; + + /** + * ITextEngine invalidates by calling invalidate() + * on Flash's Stage singleton, then validates when the Event.RENDER + * event is dispatched. + */ + function set stage(theStage:Stage):void; + + /** + * The ITextStyler instance for the engine. + * + * @see org.tinytlf.styles.ITextStyler + */ + function get styler():ITextStyler; + function set styler(textStyler:ITextStyler):void; + + /** + * Draws a selection decoration around text. + */ + function select(startIndex:Number = NaN, endIndex:Number = NaN):void; + + /** + * Invalidates the lines, decorations, and optionally the data for + * re-draw or parsing on the next screen refresh. + * + * @see org.tinytlf.layout.ITextLayout + * @see org.tinytlf.decor.ITextDecoration + * @see org.tinytlf.layout.model.ILayoutModelFactory + */ + function invalidate():void; + + /** + * An optimization to invalidate only the lines for re-draw on the next + * screen refresh. This runs ITextLayout's + * render routine, which should be optimized to only render + * the lines which the FTE's TextBlock has marked as + * invalid. + * + * @see org.tinytlf.layout.ITextLayout + */ + function invalidateLines():void; + + /** + * An optimization to invalidate only the decorations for re-draw on the + * next screen refresh. This runs the ITextDecor's + * render routine for rendering + * ITextDecorations. + * + * @see org.tinytlf.decor.ITextDecoration + */ + function invalidateDecorations():void; + + /** + * Renders the lines and decorations if they've been invalidated. + */ + function render():void; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/ITextEngineConfiguration.as b/tinytlf-core/src/org/tinytlf/ITextEngineConfiguration.as new file mode 100755 index 0000000..6c8c882 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/ITextEngineConfiguration.as @@ -0,0 +1,16 @@ +package org.tinytlf +{ + /** + * An interface that defines a method for configuring the default + * mappings for ITextEngine's member maps. You can pass an instance + * of a class which implements this interface to the ITextEngine's + * configuration property, and ITextEngine calls + * configure. + * + * @see org.tinytlf.ITextEngine + */ + public interface ITextEngineConfiguration + { + function configure(engine:ITextEngine):void; + } +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/TextEngine.as b/tinytlf-core/src/org/tinytlf/TextEngine.as new file mode 100755 index 0000000..7e779b5 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/TextEngine.as @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf +{ + import flash.display.Stage; + import flash.events.*; + import flash.geom.*; + import flash.text.engine.*; + import flash.utils.*; + + import org.tinytlf.analytics.*; + import org.tinytlf.conversion.*; + import org.tinytlf.decor.*; + import org.tinytlf.interaction.*; + import org.tinytlf.layout.*; + import org.tinytlf.styles.*; + + /** + * @see org.tinytlf.ITextEngine + */ + public class TextEngine implements ITextEngine + { + public function TextEngine(stage:Stage = null) + { + this.stage = stage; + } + + protected var _blockFactory:ITextBlockFactory; + + public function get blockFactory():ITextBlockFactory + { + if(!_blockFactory) + _blockFactory = new TextBlockFactoryBase(); + + return _blockFactory; + } + + public function set blockFactory(value:ITextBlockFactory):void + { + if(value === _blockFactory) + return; + + _blockFactory = value; + + _blockFactory.engine = this; + } + + public function set configuration(engineConfiguration:ITextEngineConfiguration):void + { + engineConfiguration.configure(this); + } + + protected var _decor:ITextDecor; + + public function get decor():ITextDecor + { + if(!_decor) + decor = new TextDecor(); + + return _decor; + } + + public function set decor(textDecor:ITextDecor):void + { + if(textDecor == _decor) + return; + + _decor = textDecor; + + _decor.engine = this; + } + + protected var _interactor:ITextInteractor; + + public function get interactor():ITextInteractor + { + if(!_interactor) + interactor = new TextInteractorBase(); + + return _interactor; + } + + public function set interactor(textInteractor:ITextInteractor):void + { + if(textInteractor == _interactor) + return; + + _interactor = textInteractor; + + _interactor.engine = this; + } + + protected var _layout:ITextLayout; + + public function get layout():ITextLayout + { + if(!_layout) + layout = new TextLayoutBase(); + + return _layout; + } + + public function set layout(textLayout:ITextLayout):void + { + if(textLayout == _layout) + return; + + _layout = textLayout; + + _layout.engine = this; + } + + protected var _stage:Stage; + + public function set stage(theStage:Stage):void + { + if(theStage === _stage) + return; + + _stage = theStage; + invalidateStage(); + } + + protected var _styler:ITextStyler; + + public function get styler():ITextStyler + { + if(!_styler) + styler = new TextStyler(); + + return _styler; + } + + public function set styler(textStyler:ITextStyler):void + { + if(textStyler == _styler) + return; + + _styler = textStyler; + + _styler.engine = this; + } + + private var _caretIndex:int = 0; + + // A unique identifier for the caret index during + // decoration, since ints are passed by value. + private const caretWrapper:Object = {caretIndex:0}; + + public function get caretIndex():int + { + return _caretIndex; + } + + public function set caretIndex(index:int):void + { + if(index === _caretIndex) + return; + + _caretIndex = Math.max(Math.min(index, blockFactory.contentVirtualizer.size), 0); + + //Don't draw the caretIndex if we don't have a caret decoration. + if(!decor.hasDecoration('caret')) + return; + + caretWrapper.caretIndex = _caretIndex; + + decor.decorate(caretWrapper, + { + caret:true, + selectionColor:styler.getStyle('caretColor'), + selectionAlpha:styler.getStyle('caretAlpha') + }, + TextDecor.CARET_LAYER, null, true); + + invalidateDecorations(); + } + + private var _scrollPosition:Number = 0; + + public function get scrollPosition():Number + { + return _scrollPosition; + } + + public function set scrollPosition(value:Number):void + { + if(value === _scrollPosition) + return; + + if(rendering) + return; + + _scrollPosition = Math.min(Math.max(value, 0), layout.textBlockVirtualizer.size); + invalidate(); + } + + private var _selection:Point = new Point(NaN, NaN); + + public function get selection():Point + { + return _selection; + } + + public function select(startIndex:Number = NaN, endIndex:Number = NaN):void + { + //super fast inline isNaN checks + if(startIndex != startIndex || endIndex != endIndex) + { + selection.x = NaN; + selection.y = NaN; + decor.undecorate(selection, "selection"); + return; + } + + var temp:Point = new Point(startIndex, endIndex); + + // Normalize the inputs. + startIndex = Math.max(Math.min(temp.x, temp.y), 0); + endIndex = Math.max(Math.max(temp.x, temp.y), 0); + + if(startIndex == selection.x && endIndex == selection.y) + return; + + selection.x = startIndex; + selection.y = endIndex; + + //Don't draw selection if we don't have a selection decoration. + if(!decor.hasDecoration('selection')) + return; + + decor.decorate(selection, + { + selection:true, + selectionColor:styler.getStyle('selectionColor'), + selectionAlpha:styler.getStyle('selectionAlpha') + }, + TextDecor.SELECTION_LAYER, null, true); + + invalidateDecorations(); + } + + public function invalidate():void + { + invalidateLines(); + invalidateDecorations(); + } + + protected var invalidateLinesFlag:Boolean = false; + + public function invalidateLines():void + { + if(invalidateLinesFlag) + return; + + invalidateLinesFlag = true; + invalidateStage(); + } + + protected var invalidateDecorationsFlag:Boolean = false; + + public function invalidateDecorations():void + { + if(invalidateDecorationsFlag) + return; + + invalidateDecorationsFlag = true; + invalidateStage(); + } + + protected function invalidateStage():void + { + if(!_stage) + return; + + _stage.addEventListener(Event.ENTER_FRAME, onRender); + _stage.addEventListener(Event.RENDER, onRender); + _stage.invalidate(); + } + + protected function onRender(event:Event):void + { + if(!_stage) + return; + + _stage.removeEventListener(Event.ENTER_FRAME, onRender); + _stage.removeEventListener(Event.RENDER, onRender); + render(); + } + + protected var rendering:Boolean = false; + + public function render():void + { + if(_stage) + { + _stage.removeEventListener(Event.ENTER_FRAME, onRender); + _stage.removeEventListener(Event.RENDER, onRender); + } + + rendering = true; + + if(invalidateLinesFlag) + renderLines(); + invalidateLinesFlag = false; + + if(invalidateDecorationsFlag) + renderDecorations(); + invalidateDecorationsFlag = false; + + rendering = false; + } + + protected var blocks:Vector.; + + protected function renderLines():void + { + // If we have selection decorations and are re-rendering the lines, + // re-render the decorations so they don't get out of sync. + if(selection.x == selection.x && selection.y == selection.y) + invalidateDecorationsFlag = true; + + blockFactory.preRender(); + layout.render(); + } + + protected function renderDecorations():void + { + layout.resetShapes(); + decor.render(); + } + } +} diff --git a/tinytlf-core/src/org/tinytlf/analytics/IVirtualizer.as b/tinytlf-core/src/org/tinytlf/analytics/IVirtualizer.as new file mode 100644 index 0000000..db3652a --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/analytics/IVirtualizer.as @@ -0,0 +1,43 @@ +package org.tinytlf.analytics +{ + import flash.utils.Dictionary; + + import org.tinytlf.ITextEngine; + + public interface IVirtualizer + { + /** + * Reference to the central ITextEngine facade for this + * decor. + * + * @see org.tinytlf.ITextEngine + */ + function get engine():ITextEngine; + function set engine(textEngine:ITextEngine):void; + + function get size():int; + function get length():int; + + function get items():Dictionary; + + function clear():void; + + function enqueue(item:*, size:int):*; + function enqueueAt(item:*, index:int, size:int):*; + + function dequeue(item:*):*; + function dequeueAt(index:int):*; + function dequeueAtPosition(position:int):*; + + function getItemStart(item:*):int; + function getItemEnd(item:*):int; + function getItemSize(item:*):int; + + function getItemIndex(item:*):int; + + function getIndexFromPosition(position:int):int; + + function getItemFromPosition(position:int):*; + function getItemFromIndex(index:int):*; + } +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/analytics/SparseArray.as b/tinytlf-core/src/org/tinytlf/analytics/SparseArray.as new file mode 100755 index 0000000..6eb374f --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/analytics/SparseArray.as @@ -0,0 +1,883 @@ +// This is a modified version of the +// spark.layouts.supportClasses.LinearLayoutVector class from the Flex 4 +// framework. The dependencies on Flex 4 have been removed, and it has been +// modified for general use. In tinytlf, I use it to enqueue TextBlocks +// for efficient scrolling. Enjoy. +package org.tinytlf.analytics +{ + /** + * A sparse array of sizes that represent items in a dimension. + * + * Provides efficient support for finding the cumulative distance to + * the start/end of an item along the axis, and similarly for finding the + * index of the item at a particular distance. + * + * Default size is used for items whose size hasn't been specified. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public final class SparseArray + { + // Assumption: vector elements (sizes) will typically be set in + // small ranges that reflect localized scrolling. Allocate vector + // elements in blocks of BLOCK_SIZE, which must be a power of 2. + // BLOCK_SHIFT is the power of 2 and BLOCK_MASK masks off as many + // low order bits. The blockTable contains all of the allocated + // blocks and has length/BLOCK_SIZE elements which are allocated lazily. + internal static const BLOCK_SIZE:uint = 128; + internal static const BLOCK_SHIFT:uint = 7; + internal static const BLOCK_MASK:uint = 0x7F; + + private const blockTable:Vector. = new Vector.(0, false); + + // Sorted Vector of intervals for the pending removes, in descending order, + // for example [7, 5, 3, 1] for the removes at 7, 6, 5, 3, 2, 1 + private var pendingRemoves:Vector. = null; + + // Sorted Vector of intervals for the pending inserts, in ascending order, + // for example [1, 3, 5, 7] for the inserts at 1, 2, 3, 5, 6, 7 + private var pendingInserts:Vector. = null; + + // What the length will be after any pending changes are flushed. + private var pendingLength:int = -1; + + public function SparseArray() + { + super(); + } + + + //-------------------------------------------------------------------------- + // + // Properties + // + //-------------------------------------------------------------------------- + + + //---------------------------------- + // length + //---------------------------------- + + private var _length:uint = 0; + + /** + * The number of item size valued elements. + * + * @default 0 + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function get length():uint + { + return pendingLength == -1 ? _length : pendingLength; + } + + /** + * @private + */ + public function set length(value:uint):void + { + flushPendingChanges(); + setLength(value); + } + + /** + * @private + * Grows or truncates the vector to be the specified newLength. + * When truncating, releases empty blocks and sets to NaN any values + * in the last block beyond the newLength. + */ + private function setLength(newLength:uint):void + { + if(newLength < _length) + { + // Clear any remaining non-NaN values in the last block + var blockIndex:uint = newLength >> BLOCK_SHIFT; + var endIndex:int = Math.min(blockIndex * BLOCK_SIZE + BLOCK_SIZE, _length) - 1; + clearInterval(newLength, endIndex); + } + + _length = newLength; + + // update the table + var partialBlock:uint = ((_length & BLOCK_MASK) == 0) ? 0 : 1; + blockTable.length = (_length >> BLOCK_SHIFT) + partialBlock; + } + + //---------------------------------- + // defaultSize + //---------------------------------- + + private var _defaultSize:Number = 0; + + /** + * The size of items whose size was not specified with setItemSize. + * + * @default 0 + * @see #cacheDimensions + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function get defaultSize():Number + { + return _defaultSize; + } + + /** + * @private + */ + public function set defaultSize(value:Number):void + { + _defaultSize = value; + } + + //---------------------------------- + // axisOffset + //---------------------------------- + + private var _axisOffset:Number = 0; + + /** + * The offset of the first item from the origin in the majorAxis + * direction. This is useful when implementing padding, + * in addition to gaps, for virtual layouts. + * + * @see #gap + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function get axisOffset():Number + { + return _axisOffset; + } + + /** + * @private + */ + public function set axisOffset(value:Number):void + { + _axisOffset = value; + } + + //---------------------------------- + // gap + //---------------------------------- + + private var _gap:Number = 0; + + /** + * The distance between items. + * + * @default 0 + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function get gap():Number + { + return _gap; + } + + /** + * @private + */ + public function set gap(value:Number):void + { + _gap = value; + } + + //-------------------------------------------------------------------------- + // + // Methods + // + //-------------------------------------------------------------------------- + + /** + * Return the size of the item at index. If no size was ever + * specified then then the defaultSize is returned. + * + * @param index The item's index. + * @see defaultSize + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function getItemSize(index:uint):Number + { + flushPendingChanges(); + + var block:Block = blockTable[index >> BLOCK_SHIFT]; + if(block) + { + var value:Number = block.sizes[index & BLOCK_MASK]; + return (isNaN(value)) ? _defaultSize : value; + } + else + return _defaultSize; + } + + /** + * Set the size of the item at index. If an index is + * set to NaN then subsequent calls to get + * will return the defaultSize. + * + * @param index The item's index. + * @param value The item's size. + * @see defaultSize + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function setItemSize(index:uint, value:Number):void + { + flushPendingChanges(); + + if(index >= length) + throw new Error("Invalid index and all that."); + + var blockIndex:uint = index >> BLOCK_SHIFT; + var block:Block = blockTable[blockIndex]; + if(!block) + block = blockTable[blockIndex] = new Block(); + + var sizesIndex:uint = index & BLOCK_MASK; + var sizes:Vector. = block.sizes; + var oldValue:Number = sizes[sizesIndex]; + if(oldValue == value) + return; + + if(isNaN(oldValue)) + { + block.defaultCount -= 1; + block.sizesSum += value; + } + else if(isNaN(value)) + { + block.defaultCount += 1; + block.sizesSum -= oldValue; + } + else + block.sizesSum += value - oldValue; + + sizes[sizesIndex] = value; + } + + /** + * Make room for a new item at index by shifting all of the sizes + * one position to the right, beginning with startIndex. + * + * The value at index will be NaN. + * + * This is similar to array.splice(index, 0, NaN). + * + * @param index The position of the new NaN size item. + */ + public function insert(index:uint):void + { + // We don't support interleaved pending inserts and removes + if(pendingRemoves) + flushPendingChanges(); + + if(pendingInserts) + { + // Update the last interval or add a new one? + var lastIndex:int = pendingInserts.length - 1; + var intervalEnd:int = pendingInserts[lastIndex]; + + if(index == intervalEnd + 1) + { + // Extend the end of the interval + pendingInserts[lastIndex] = index; + } + else if(index > intervalEnd) + { + // New interval + pendingInserts.push(index); + pendingInserts.push(index); + } + else + { + // We can't support pending inserts that are not ascending + flushPendingChanges(); + } + } + + pendingLength = Math.max(length + 1, index + 1); + + if(!pendingInserts) + { + pendingInserts = new Vector.(); + pendingInserts.push(index); + pendingInserts.push(index); + } + } + + /** + * Remove index by shifting all of the sizes one position to the left, + * begining with index+1. + * + * This is similar to array.splice(index, 1). + * + * @param index The position to be removed. + */ + public function remove(index:uint):void + { + // We don't support interleaved pending inserts and removes + if(pendingInserts) + flushPendingChanges(); + + // length getter takes into account pending inserts/removes but doesn't flush + if(index >= length) + throw new Error("Invalid index and all that."); + + if(pendingRemoves) + { + // Update the last interval or add a new one? + var lastIndex:int = pendingRemoves.length - 1; + var intervalStart:int = pendingRemoves[lastIndex]; + + if(index == intervalStart - 1) + { + // Extend the start of the interval + pendingRemoves[lastIndex] = index; + } + else if(index < intervalStart) + { + // New interval + pendingRemoves.push(index); + pendingRemoves.push(index); + } + else + { + // We can't support pending removes that are not descending + flushPendingChanges(); + } + } + + pendingLength = (pendingLength == -1) ? length - 1 : pendingLength - 1; + + if(!pendingRemoves) + { + pendingRemoves = new Vector.(); + pendingRemoves.push(index); + pendingRemoves.push(index); + } + } + + /** + * @private + * Returns true when all sizes in the specified interval for the block are NaN + */ + private function isIntervalClear(block:Block, index:int, count:int):Boolean + { + var sizesSrc:Vector. = block.sizes; + for(var i:int = 0; i < count; i++) + { + if(!isNaN(sizesSrc[index + i])) + return true; + } + return false; + } + + /** + * @private + * Copies elements between blocks. Indices relative to the blocks. + * If srcBlock is null, then it fills the destination with NaNs. + * The case of srcBlock == dstBlock is also supported. + * The caller must ensure that count is within range. + */ + private function inBlockCopy(dstBlock:Block, dstIndexStart:int, srcBlock:Block, srcIndexStart:int, count:int):void + { + var ascending:Boolean = dstIndexStart < srcIndexStart; + + var srcIndex:int = ascending ? srcIndexStart : srcIndexStart + count - 1; + var dstIndex:int = ascending ? dstIndexStart : dstIndexStart + count - 1; + var increment:int = ascending ? +1 : -1; + + var dstSizes:Vector. = dstBlock.sizes; + var srcSizes:Vector. = srcBlock ? srcBlock.sizes : null; + var dstValue:Number = NaN; + var srcValue:Number = NaN; + var sizesSumDelta:Number = 0; // How much the destination sizesSum will change + var defaultCountDelta:int = 0; // How much the destination defaultCount will change + + while(count > 0) + { + if(srcSizes) + srcValue = srcSizes[srcIndex]; + dstValue = dstSizes[dstIndex]; + + // Are the values different? + if(!(srcValue === dstValue)) // Triple '=' to handle NaN comparison + { + // Are we removing a default size or a chached size? + if(isNaN(dstValue)) + defaultCountDelta--; + else + sizesSumDelta -= dstValue; + + // Are we adding a default size or a cached size? + if(isNaN(srcValue)) + defaultCountDelta++; + else + sizesSumDelta += srcValue; + + dstSizes[dstIndex] = srcValue; + } + + srcIndex += increment; + dstIndex += increment; + count--; + } + + dstBlock.sizesSum += sizesSumDelta; + dstBlock.defaultCount += defaultCountDelta; + } + + /** + * @private + * Copies 'count' elements from dstIndex to srcIndex. + * Safe for overlapping source and destination intervals. + * If any blocks are left full of NaNs, they will be deallcated. + */ + private function copyInterval(dstIndex:int, srcIndex:int, count:int):void + { + var ascending:Boolean = dstIndex < srcIndex; + if(!ascending) + { + dstIndex += count - 1; + srcIndex += count - 1; + } + + while(count > 0) + { + // Figure out destination block + var dstBlockIndex:uint = dstIndex >> BLOCK_SHIFT; + var dstSizesIndex:uint = dstIndex & BLOCK_MASK; + var dstBlock:Block = blockTable[dstBlockIndex]; + + // Figure out source block + var srcBlockIndex:uint = srcIndex >> BLOCK_SHIFT; + var srcSizesIndex:uint = srcIndex & BLOCK_MASK; + var srcBlock:Block = blockTable[srcBlockIndex]; + + // Figure out number of elements to copy + var copyCount:int; + if(ascending) + copyCount = Math.min(BLOCK_SIZE - dstSizesIndex, BLOCK_SIZE - srcSizesIndex); + else + copyCount = 1 + Math.min(dstSizesIndex, srcSizesIndex); + copyCount = Math.min(copyCount, count); + + // Figure out the start index for each block + var dstStartIndex:int = ascending ? dstSizesIndex : dstSizesIndex - copyCount + 1; + var srcStartIndex:int = ascending ? srcSizesIndex : srcSizesIndex - copyCount + 1; + + // Check whether a destination block needs to be allocated. + // Allocate only if there are non-default values to be copied from the source. + if(srcBlock && !dstBlock && + isIntervalClear(srcBlock, srcStartIndex, copyCount)) + { + dstBlock = new Block(); + blockTable[dstBlockIndex] = dstBlock; + } + + // Copy to non-null dstBlock, srcBlock can be null + if(dstBlock) + { + inBlockCopy(dstBlock, dstStartIndex, srcBlock, srcStartIndex, copyCount); + + // If this is the last time we're visiting this block, and it contains + // only NaNs, then remove it + if(dstBlock.defaultCount == BLOCK_SIZE) + { + var blockEndReached:Boolean = ascending ? (dstStartIndex + copyCount == BLOCK_SIZE) : + (dstStartIndex == 0); + if(blockEndReached || count == copyCount) + blockTable[dstBlockIndex] = null; + } + } + + dstIndex += ascending ? copyCount : -copyCount; + srcIndex += ascending ? copyCount : -copyCount; + count -= copyCount; + } + } + + /** + * @private + * Sets all elements within the specified interval to NaN (both ends inclusive). + * Releases empty blocks. + */ + private function clearInterval(start:int, end:int):void + { + while(start <= end) + { + // Figure our destination block + var blockIndex:uint = start >> BLOCK_SHIFT; + var sizesIndex:uint = start & BLOCK_MASK; + var block:Block = blockTable[blockIndex]; + + // Figure out number of elements to clear in this iteration + // Make sure we don't clear more items than requested + var clearCount:int = BLOCK_SIZE - sizesIndex; + clearCount = Math.min(clearCount, end - start + 1); + + if(block) + { + if(clearCount == BLOCK_SIZE) + blockTable[blockIndex] = null; + else + { + // Copying from null source block is equivalent of clearing the destination block + inBlockCopy(block, sizesIndex, null /*srcBlock*/, 0, clearCount); + + // If the blockDst contains only default sizes, then remove the block + if(block.defaultCount == BLOCK_SIZE) + blockTable[blockIndex] = null; + } + } + + start += clearCount; + } + } + + /** + * @private + * Removes the elements designated by the intervals and truncates + * the LinearLayoutVector to the new length. + * 'intervals' is a Vector of descending intervals [7, 5, 3, 1] + */ + private function removeIntervals(intervals:Vector.):void + { + var intervalsCount:int = intervals.length; + if(intervalsCount == 0) + return; + + // Adding final nextIntervalStart value (see below). + intervals.reverse(); // turn into ascending, for example [7, 5, 3, 1] --> [1, 3, 5, 7] + intervals.push(length); + + // Move the elements + var dstStart:int = intervals[0]; + var srcStart:int; + var count:int; + var i:int = 0; + do + { + var intervalEnd:int = intervals[i + 1]; + var nextIntervalStart:int = intervals[i + 2] + i += 2; + + // Start copy from after the end of current interval + srcStart = intervalEnd + 1; + + // copy all elements up to the start of the next interval. + count = nextIntervalStart - srcStart; + + copyInterval(dstStart, srcStart, count); + dstStart += count; + } while(i < intervalsCount) + + // Truncate the excess elements. + setLength(dstStart); + } + + /** + * @private + * Increases the length and inserts NaN values for the elements designated by the intervals. + * 'intervals' is a Vector of ascending intervals [1, 3, 5, 7] + */ + private function insertIntervals(intervals:Vector., newLength:int):void + { + var intervalsCount:int = intervals.length; + if(intervalsCount == 0) + return; + + // Allocate enough space for the insertions, all the elements + // allocated are NaN by default. + var oldLength:int = length; + setLength(newLength); + + var srcEnd:int = oldLength - 1; + var dstEnd:int = newLength - 1; + var i:int = intervalsCount - 2; + while(i >= 0) + { + // Find current interval + var intervalStart:int = intervals[i]; + var intervalEnd:int = intervals[i + 1]; + i -= 2; + + // Start after the current interval + var dstStart:int = intervalEnd + 1; + var copyCount:int = dstEnd - dstStart + 1; + var srcStart:int = srcEnd - copyCount + 1; + + copyInterval(dstStart, srcStart, copyCount); + dstStart -= copyCount; + dstEnd = intervalStart - 1; + + // Fill in with default NaN values after the copy + clearInterval(intervalStart, intervalEnd); + } + } + + /** + * @private + * Processes any pending removes or pending inserts. + */ + private function flushPendingChanges():void + { + var intervals:Vector.; + if(pendingRemoves) + { + intervals = pendingRemoves; + pendingRemoves = null; + pendingLength = -1; + removeIntervals(intervals); + } + else if(pendingInserts) + { + intervals = pendingInserts; + var newLength:int = pendingLength; + pendingInserts = null; + pendingLength = -1; + insertIntervals(intervals, newLength); + } + } + + /** + * The cumulative distance to the start of the item at index, including + * the gaps between items and the axisOffset. + * + * The value of start(0) is axisOffset. + * + * Equivalent to: + *
    +		 * var distance:Number = axisOffset;
    +		 * for (var i:int = 0; i < index; i++)
    +		 *     distance += get(i);
    +		 * return distance + (gap * index);
    +		 * 
    + * + * The actual implementation is relatively efficient. + * + * @param index The item's index. + * @see #end + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function start(index:uint):Number + { + flushPendingChanges(); + + if((_length == 0) || (index == 0)) + return axisOffset; + + if(index >= _length) + throw new Error("Invalid index and all that."); + + var distance:Number = axisOffset; + var blockIndex:uint = index >> BLOCK_SHIFT; + for(var i:int = 0; i < blockIndex; i++) + { + var block:Block = blockTable[i]; + if(block) + distance += block.sizesSum + (block.defaultCount * _defaultSize); + else + distance += BLOCK_SIZE * _defaultSize; + } + var lastBlock:Block = blockTable[blockIndex]; + var lastBlockOffset:uint = index & ~BLOCK_MASK; + var lastBlockLength:uint = index - lastBlockOffset; + if(lastBlock) + { + var sizes:Vector. = lastBlock.sizes; + for(i = 0; i < lastBlockLength; i++) + { + var size:Number = sizes[i]; + distance += (isNaN(size)) ? _defaultSize : size; + } + } + else + distance += _defaultSize * lastBlockLength; + distance += index * gap; + return distance; + } + + /** + * The cumulative distance to the end of the item at index, including + * the gaps between items. + * + * If index <(length-1) then the value of this + * function is defined as: + * start(index) + get(index). + * + * @param index The item's index. + * @see #start + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function end(index:uint):Number + { + flushPendingChanges(); + return start(index) + getItemSize(index); + } + + /** + * Returns the index of the item that overlaps the specified distance. + * + * The item at index i overlaps a distance value + * if start(i) <= distance < end(i). + * + * If no such item exists, -1 is returned. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ + public function indexOf(distance:Number):int + { + flushPendingChanges(); + var index:int = indexOfInternal(distance); + return (index >= _length) ? -1 : index; + } + + private function indexOfInternal(distance:Number):int + { + if((_length == 0) || (distance < 0)) + return -1; + + // The area of the first item includes the axisOffset + var curDistance:Number = axisOffset; + if(distance < curDistance) + return 0; + + var index:int = -1; + var block:Block = null; + var blockGap:Number = _gap * BLOCK_SIZE; + + // Find the block that contains distance and the index of its + // first element + for(var blockIndex:uint = 0; blockIndex < blockTable.length; blockIndex++) + { + block = blockTable[blockIndex]; + var blockDistance:Number = blockGap; + if(block) + blockDistance += block.sizesSum + (block.defaultCount * _defaultSize); + else + blockDistance += BLOCK_SIZE * _defaultSize; + if((distance == curDistance) || + ((distance >= curDistance) && (distance < (curDistance + blockDistance)))) + { + index = blockIndex << BLOCK_SHIFT; + break; + } + curDistance += blockDistance; + } + + if((index == -1) || (distance == curDistance)) + return index; + + // At this point index corresponds to the first item in this block + if(block) + { + // Find the item that contains distance and return its index + var sizes:Vector. = block.sizes; + for(var i:int = 0; i < BLOCK_SIZE - 1; i++) + { + var size:Number = sizes[i]; + curDistance += _gap + (isNaN(size) ? _defaultSize : size); + if(curDistance > distance) + return index + i; + } + // TBD special-case for the very last index + return index + BLOCK_SIZE - 1; + } + else + { + return index + Math.floor(Number(distance - curDistance) / Number(_defaultSize + _gap)); + } + } + + /** + * Clear all cached state, reset length to zero. + */ + public function clear():void + { + // Discard any pending changes, before setting the length + // otherwise the length setter will commit the changes. + pendingRemoves = null; + pendingInserts = null; + pendingLength = -1; + + length = 0; // clears the BlockTable as well + } + + public function toString():String + { + return "LinearLayoutVector{" + + "length=" + _length + + " [blocks=" + blockTable.length + "]" + + " gap=" + _gap + + " defaultSize=" + _defaultSize + + " pendingRemoves=" + (pendingRemoves ? pendingRemoves.length : 0) + + " pendingInserts=" + (pendingInserts ? pendingInserts.length : 0) + + "}"; + } + } +} + +/** + * @private + * A SparseArray block of layout element heights or widths. + * + * Total "distance" for a Block is: sizesSum + (defaultCount * distanceVector.default). + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4 + */ +internal final class Block +{ + internal const sizes:Vector. = new Vector.(BLOCK_SIZE, true); + internal var sizesSum:Number = 0; + internal var defaultCount:uint = BLOCK_SIZE; + + public function Block() + { + super(); + for(var i:int = 0; i < BLOCK_SIZE; i += 1) + sizes[i] = NaN; + } + + internal static const BLOCK_SIZE:uint = 128; +} + diff --git a/tinytlf-core/src/org/tinytlf/analytics/Virtualizer.as b/tinytlf-core/src/org/tinytlf/analytics/Virtualizer.as new file mode 100644 index 0000000..8fa653a --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/analytics/Virtualizer.as @@ -0,0 +1,132 @@ +package org.tinytlf.analytics +{ + import flash.utils.Dictionary; + + import org.tinytlf.ITextEngine; + + public class Virtualizer implements IVirtualizer + { + private var vector:SparseArray = new SparseArray(); + private var indexCache:Array = []; + private var itemCache:Dictionary = new Dictionary(false); + + protected var _engine:ITextEngine; + + public function get engine():ITextEngine + { + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if (textEngine == _engine) + return; + + _engine = textEngine; + } + + public function get size():int + { + return vector.end(vector.length - 1); + } + + public function get length():int + { + return indexCache.length; + } + + public function get items():Dictionary + { + return itemCache; + } + + public function clear():void + { + vector = new SparseArray(); + indexCache = []; + itemCache = new Dictionary(false); + } + + public function enqueue(item:*, size:int):* + { + return enqueueAt(item, length, size); + } + + public function enqueueAt(item:*, index:int, size:int):* + { + if(item in itemCache) + return item; + + if(size <= 0) + size = 1; + + indexCache.splice(index, 0, item); + itemCache[item] = true; + vector.insert(index); + vector.setItemSize(index, size); + return item; + } + + public function dequeue(item:*):* + { + dequeueAt(getItemIndex(item)); + } + + public function dequeueAt(index:int):* + { + var item:* = getItemFromIndex(index); + + if(!item || !(item in itemCache)) + return item; + + indexCache.splice(index, 1); + vector.remove(index); + delete itemCache[item]; + + return item; + } + + public function dequeueAtPosition(position:int):* + { + return dequeueAt(getItemIndex(getItemFromPosition(position))); + } + + public function getItemIndex(item:*):int + { + return indexCache.indexOf(item); + } + + public function getItemStart(item:*):int + { + return vector.start(getItemIndex(item)); + } + + public function getItemEnd(item:*):int + { + return vector.end(getItemIndex(item)); + } + + public function getItemSize(item:*):int + { + return vector.getItemSize(getItemIndex(item)); + } + + public function getIndexFromPosition(position:int):int + { + return vector.indexOf(position); + } + + public function getItemFromPosition(position:int):* + { + return getItemFromIndex(getIndexFromPosition(position)); + } + + public function getItemFromIndex(index:int):* + { + if(index >= length) + return null; + + return indexCache[index]; + } + } +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/conversion/ContentElementFactory.as b/tinytlf-core/src/org/tinytlf/conversion/ContentElementFactory.as new file mode 100755 index 0000000..eff777e --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/conversion/ContentElementFactory.as @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.conversion +{ + import flash.display.*; + import flash.events.EventDispatcher; + import flash.text.engine.*; + + import org.tinytlf.ITextEngine; + import org.tinytlf.util.fte.ContentElementUtil; + + public class ContentElementFactory implements IContentElementFactory + { + public function execute(data:Object, ... context:Array):ContentElement + { + var element:ContentElement; + + //If the data is an empty string, insert a placeholder GraphicElement. + if(data && data.toString() === "") + { + element = new GraphicElement(new Shape(), 0, 0, getElementFormat(context), getEventMirror(context)); + } + else if(data is String) + { + element = new TextElement(String(data), getElementFormat(context), getEventMirror(context)); + element.userData = context; + } + else if(data is Vector.) + { + element = new GroupElement(Vector.(data), getElementFormat(context)); + element.userData = context; + } + + if(!element) + return null; + + if(!(element is GroupElement)) + { + //Do any decorations for this element + var dec:Object = engine.styler.describeElement(context.length ? context : null); + if(dec != null) + { + engine.decor.decorate(element, dec, dec.layer, null, dec.foreground); + } + } + + return element; + } + + private var _engine:ITextEngine; + + public function get engine():ITextEngine + { + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if(textEngine === _engine) + return; + + _engine = textEngine; + } + + protected function getElementFormat(context:Object):ElementFormat + { + // You can't render a textLine with a null ElementFormat, so return an empty one here. + if(!_engine) + return new ElementFormat(); + + return engine.styler.getElementFormat(context); + } + + protected function getEventMirror(context:Object):EventDispatcher + { + if(!_engine) + return null; + + return engine.interactor.getMirror(context); + } + + protected function get ef():ElementFormat + { + return new ElementFormat(); + } + } +} + diff --git a/tinytlf-core/src/org/tinytlf/conversion/IContentElementFactory.as b/tinytlf-core/src/org/tinytlf/conversion/IContentElementFactory.as new file mode 100755 index 0000000..8108e1f --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/conversion/IContentElementFactory.as @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.conversion +{ + import flash.text.engine.ContentElement; + + import org.tinytlf.ITextEngine; + + /** + * IContentElementFactory is an interface for parsing arbitrary data into + * Flash Text Engine ContentElements. Classes of this type are mapped to the + * ILayoutFactoryMap for element types, then the ILayoutFactoryMap + * implementation calls execute for each matching data type. + */ + public interface IContentElementFactory + { + /** + *

    + * Reference to the central ITextEngine facade for this + * contentElementFactory. + *

    + * + * @see org.tinytlf.ITextEngine + */ + function get engine():ITextEngine; + function set engine(textEngine:ITextEngine):void; + + /** + * Returns a Flash Text Engine ContentElement for the data object. + * Accepts arbitrary contextual parameters which implementations can + * harness to create specialized ContentElements. + * + * @see org.tinytlf.layout.model.factories.LayoutFactoryMap#createBlocks() + */ + function execute(data:Object, ...context:Array):ContentElement; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/conversion/ITextBlockFactory.as b/tinytlf-core/src/org/tinytlf/conversion/ITextBlockFactory.as new file mode 100755 index 0000000..af7928d --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/conversion/ITextBlockFactory.as @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.conversion +{ + import flash.text.engine.TextBlock; + + import org.tinytlf.ITextEngine; + import org.tinytlf.analytics.IVirtualizer; + + /** + * ITextBlockFactory parses arbitrary data and returns a Vector of + * TextBlocks to tinytlf. Since generating TextBlocks also requires + * generating FTE ContentElements, ITextBlockFactory exposes a map and a + * command pattern which can be harnessed while parsing hierarchical data. + * + *

    + * ITextBlockFactory exposes a map which lets you define + * IContentElementFactories for an "element." The "element" + * type is unimportant, it just acts as a key for retrieving the factory. + *

    + *

    + * No matter what your data source, it's more than likely a tree of some + * sort, since rich text is most easily represented as some sort of tree. + * Tinytlf provides the ITextBlockFactory so you can generically traverse + * the tree, creating a corresponding tree of ContentElements. At each step + * in the recursion, if you call into the ITextBlockFactory, + * requesting the IContentElementFactory for the corresponding + * node, you have an algorithm that accepts external modification, but can + * convert from your specific data structure to FTE + * ContentElements. + *

    + */ + public interface ITextBlockFactory + { + /** + * Reference to the central ITextEngine facade for this + * factory. + * + * @see org.tinytlf.ITextEngine + */ + function get engine():ITextEngine; + function set engine(textEngine:ITextEngine):void; + + function get data():Object; + function set data(value:Object):void; + + function get textBlockGenerator():ITextBlockGenerator; + function set textBlockGenerator(value:ITextBlockGenerator):void; + + function get contentVirtualizer():IVirtualizer; + + /** + * The number of potential discreet TextBlock instances that this + * ITextBlockFactory can create. This number is incremented + * and decremented when you make calls to addBlockData/removeBlockData. + * + * This does not represent the number of TextBlocks currently in + * existance, only the number of potential blocks. + */ + function get numBlocks():int; + + /** + * Called during the ITextEngine render phase, just before the + * ITextLayoutBase#render() method is called. + * + * This provides an opportunity to invalidate the visible TextBlocks, + * gather up orphan lines, and anything else that the render algorithm + * may depend on being done. + */ + function preRender():void; + + /** + * Returns a reference to the next visible TextBlock. This method should + * generate the TextBlock on the fly, as keeping references to every + * TextBlock in the TextField is potentially expensive. + */ + function getTextBlock(index:int):TextBlock; + + /** + * Checks to see if an IContentElementFactory class has + * mapped for the given element. + * + * @Returns true if a factory has been mapped, false otherwise. + * + * @see org.tinytlf.layout.model.factories.IContentElementFactory + */ + function hasElementFactory(element:*):Boolean; + + /** + * Returns an IContentElementFactory instance for the given element. + * + * @see org.tinytlf.layout.model.factories.IContentElementFactory + */ + function getElementFactory(element:*):IContentElementFactory; + + /** + * Maps an IContentElementFactory for the given element. + * + * @see org.tinytlf.layout.model.factories.IContentElementFactory + */ + function mapElementFactory(element:*, classOrFactory:Object):void; + + /** + * Unmaps the given element. + * + * @see org.tinytlf.layout.model.factories.IContentElementFactory + */ + function unMapElementFactory(element:*):Boolean; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/conversion/ITextBlockGenerator.as b/tinytlf-core/src/org/tinytlf/conversion/ITextBlockGenerator.as new file mode 100644 index 0000000..3d0ab10 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/conversion/ITextBlockGenerator.as @@ -0,0 +1,9 @@ +package org.tinytlf.conversion +{ + import flash.text.engine.TextBlock; + + public interface ITextBlockGenerator + { + function generate(data:*, factory:IContentElementFactory):TextBlock; + } +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/conversion/TextBlockFactoryBase.as b/tinytlf-core/src/org/tinytlf/conversion/TextBlockFactoryBase.as new file mode 100755 index 0000000..c4c8286 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/conversion/TextBlockFactoryBase.as @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.conversion +{ + import flash.text.engine.*; + import flash.utils.Dictionary; + + import org.tinytlf.*; + import org.tinytlf.analytics.*; + import org.tinytlf.layout.properties.*; + + public class TextBlockFactoryBase implements ITextBlockFactory + { + private var _engine:ITextEngine; + + public function get engine():ITextEngine + { + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if(textEngine == _engine) + return; + + _engine = textEngine; + } + + protected var virtualizer:IVirtualizer = new Virtualizer(); + + public function get contentVirtualizer():IVirtualizer + { + return virtualizer; + } + + public function preRender():void + { + } + + public function getTextBlock(index:int):TextBlock + { + var block:TextBlock = contentVirtualizer.getItemFromIndex(index) as TextBlock; + + if(block) + return block; + + return textBlockGenerator.generate(data, getElementFactory(data)); + } + + protected var elementAdapterMap:Dictionary = new Dictionary(false); + + public function hasElementFactory(element:*):Boolean + { + return Boolean(element in elementAdapterMap); + } + + public function getElementFactory(element:*):IContentElementFactory + { + var adapter:*; + + //Return the generic adapter if we haven't mapped any. + if(!hasElementFactory(element)) + { + adapter = new ContentElementFactory(); + IContentElementFactory(adapter).engine = engine; + return adapter; + } + + adapter = elementAdapterMap[element]; + if(adapter is Class) + adapter = IContentElementFactory(new (adapter as Class)()); + if(adapter is Function) + adapter = IContentElementFactory((adapter as Function)()); + + IContentElementFactory(adapter).engine = engine; + + return IContentElementFactory(adapter); + } + + public function mapElementFactory(element:*, classOrFactory:Object):void + { + elementAdapterMap[element] = classOrFactory; + } + + public function unMapElementFactory(element:*):Boolean + { + if(!(element in elementAdapterMap)) + return false; + + return delete elementAdapterMap[element]; + } + + protected var _data:Object; + + public function get data():Object + { + return _data; + } + + public function set data(value:Object):void + { + if(value == _data) + return; + + _data = value; + if(engine) + engine.invalidate(); + } + + private var generator:ITextBlockGenerator = new TextBlockGenerator(); + + public function get textBlockGenerator():ITextBlockGenerator + { + return generator; + } + + public function set textBlockGenerator(blockGenerator:ITextBlockGenerator):void + { + if(blockGenerator == generator) + return; + + generator = blockGenerator; + } + + public function get numBlocks():int + { + return 0; + } + } +} +import flash.text.engine.TextBlock; + +import org.tinytlf.ITextEngine; +import org.tinytlf.conversion.*; +import org.tinytlf.layout.properties.LayoutProperties; +import org.tinytlf.util.TinytlfUtil; +import org.tinytlf.util.fte.TextBlockUtil; + +internal class TextBlockGenerator implements ITextBlockGenerator +{ + public function generate(data:*, factory:IContentElementFactory):TextBlock + { + var block:TextBlock = TextBlockUtil.checkOut(); + block.content = factory.execute(data); + + var props:LayoutProperties = TinytlfUtil.getLP(); + props.mergeWith(data); + props.applyTo(block); + props.model = data; + block.userData = props; + + return block; + } +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/decor/ITextDecor.as b/tinytlf-core/src/org/tinytlf/decor/ITextDecor.as new file mode 100755 index 0000000..b12a326 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/decor/ITextDecor.as @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.decor +{ + import org.tinytlf.ITextEngine; + import org.tinytlf.layout.ITextContainer; + + /** + * ITextDecor is the decoration actor for tinytlf. ITextDecor + * is a map for defining text decorations, including what they're called and + * the classes or instances which draw the decoration. + * ITextDecor also manages the application and rendering of + * decorations. Use decorate and undecorate to + * apply or un-apply a decoration to an element. ITextDecor + * handles invalidating and rendering the decorations. + */ + public interface ITextDecor + { + /** + * Reference to the central ITextEngine facade for this + * decor. + * + * @see org.tinytlf.ITextEngine + */ + function get engine():ITextEngine; + function set engine(textEngine:ITextEngine):void; + + /** + * Renders all the decorations in the text field. + * + * @see org.tinytlf.ITextEngine#invalidateDecorations() + */ + function render():void; + + /** + * Removes all the decorations in the text field. + */ + function removeAll():void; + + /** + * Creates a decoration at the specified layer for the + * element based on the styleObj. An optional + * containers argument can be supplied if it is known which + * ITextContainers this decoration will render onto. This + * is an optimization, as the ITextContainers can be + * determined at render time, though it can be an expensive operation. + * + *

    + * The element type is deliberately ambiguous, as it's up to the + * implementor to support different types. In the core + * TextDecor, element can be one of four + * types: + *

      + *
    1. A flash.text.engine.ContentElement.
    2. + *
    3. A flash.text.engine.TextLine.
    4. + *
    5. A flash.geom.Rectangle.
    6. + *
    7. A Vector of flash.geom.Rectangles.
    8. + *
    + * If the type is a ContentElement, the optional + * containers vector is ignored, as + * ITextContainers can't be associated for a + * ContentElement until render time. + *

    + * + *

    + * The layer property defines which layer the decoration + * should exist on. Smaller numbers are closer to the z-order top, with + * 0 being the "highest" allowed layer. The ITextEngine's + * caret decoration is on layer 0, and the selection decoration is on + * layer 1. + *

    + * + * @see org.tinytlf.decor.TextDecor + */ + function decorate(element:*, styleObject:Object, layer:int = 3, + container:ITextContainer = null, foreground:Boolean = false):void; + + /** + * Undecorate has three expected functions: + *
      + *
    • If element and decorationProp are + * specified, undecorate removes a the specified + * decorationProp from the element.
    • + *
    • If element is specified with no + * decorationProp, undecorate removes all + * decorations for the element.
    • + *
    • If decorationProp is specified with no + * element, undecorate removes all instances + * of the decoration from all elements in the + * ITextDecor.
    • + *
    + * + * @see org.tinytlf.decor.ITextDecoration + */ + function undecorate(element:* = null, decorationProp:String = null):void; + + /** + * Associates the specified decorationProp with the + * decorationClassOrFactory (decoration). The + * decoration can be a Class reference for an object which + * implements org.tinytlf.decor.ITextDecoration, or a + * Function which returns an object that implements + * org.tinytlf.decor.ITextDecoration. + * If decoration is a Class reference, the + * decoration is instantiated and returned. If + * decoration is a Function, the Function is called and the + * return value used as the ITextDecoration instance. + * + * @see org.tinytlf.decor.ITextDecoration + */ + function mapDecoration(decorationProp:String, + decorationClassOrFactory:Object):void; + + /** + * Unmaps a decoration for the specified decorationProp. + * @returns True if decorationProp was successfully + * unmapped, False if there was an error or there was no + * ITextDecoration mapped for this + * decorationProp. + */ + function unMapDecoration(decorationProp:String):Boolean; + + /** + * Checks to see if an ITextDecoration has been mapped for + * the specified decorationProp. + * @returns True if there is an ITextDecoration, False if + * there isn't. + */ + function hasDecoration(decorationProp:String):Boolean; + + /** + * Gets or instantiates an instance of an ITextDecoration + * for the specified decorationProp. Optionally associates + * the specified ITextContainer with the decoration. + */ + function getDecoration(decorationProp:String, container:ITextContainer = null):ITextDecoration; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/decor/ITextDecoration.as b/tinytlf-core/src/org/tinytlf/decor/ITextDecoration.as new file mode 100755 index 0000000..7648cef --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/decor/ITextDecoration.as @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.decor +{ + import flash.events.IEventDispatcher; + import flash.geom.Rectangle; + + import org.tinytlf.ITextEngine; + import org.tinytlf.layout.ITextContainer; + import org.tinytlf.styles.IStyleAware; + + /** + *

    + * ITextDecoration is the interface for objects which draw + * decorations to the display list. + *

    + *

    + * An ITextDecoration is a single text decoration, such as a + * background color, underline, etc. A decoration draws onto the + * shapes Sprite of an ITextContainer. The + * decoration's setup method preps the decoration for rendering + * and returns a Vector of flash.geom.Rectangles which + * represent the area within which this decoration should render. The + * decoration's draw method is passed the output from the + * setup method, and should use the + * flash.geom.Rectangles for guidance in rendering decorations. + *

    + *

    + * Because tinytlf supports multi-container layouts, a decoration can span + * DisplayObjectContainers. Therefore it is necessary to + * specify a vector of associated ITextContainer instances for + * decorations, so they know which Sprites to draw into. + *

    + *

    + * Note: A decoration shares the same layer sprite with other decoration + * instances. It is not necessary to call graphics.clear() in the + * draw, that will clear the previously rendered decorations. + * The ITextContainer should take care of clearing the display + * list before each rendering cycle. + *

    + */ + public interface ITextDecoration extends IStyleAware + { + function get container():ITextContainer; + function set container(textContainer:ITextContainer):void; + + function get engine():ITextEngine; + function set engine(textEngine:ITextEngine):void; + + /** + * Whether this decoration is in the foreground (on top of the TextLines) + * or in the background (behind the TextLines). + */ + function set foreground(value:Boolean):void; + + /** + *

    + * Prepares this decoration for rendering. Setup takes its + * input and should return a Vector of + * flash.geom.Rectangles which can be passed to + * draw. + *

    + *

    + * Setup accepts a variable number of + * arguments, but in tinytlf core, the second argument is the + * element passed into ITextDecor#decorate(). + *

    + *

    + * This interface is non-restrictive. If there's more information that + * your custom ITextDecoration needs, feel free to pass it + * in. + *

    + * + * @see org.tinytlf.decor.ITextDecor#decorate() + */ + function setup(layer:int = 2, ...args):Vector.; + + /** + *

    + * Renders the decoration to the display list. Draw accepts + * a Vector of flash.geom.Rectangles which represent the + * area within which to render the decoration. For example, an underline + * decoration would draw a thin line along the bottom of each + * Rectangle. + *

    + */ + function draw(bounds:Vector.):void; + + /** + * Called when a decoration is removed from an element. Cleans up any + * artifacts left behind from the decoration. + * + * @see org.tinytlf.decor.ITextDecor#undecorate + */ + function destroy():void; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/decor/TextDecor.as b/tinytlf-core/src/org/tinytlf/decor/TextDecor.as new file mode 100755 index 0000000..24aa373 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/decor/TextDecor.as @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.decor +{ + import org.tinytlf.ITextEngine; + import org.tinytlf.layout.ITextContainer; + + //// + // This renders every decoration again every time the decorations are + // invalidated. Isn't there a way to know which decorations changed and + // only clear/render those? I mean without creating separate + // Shapes/Sprites per decoration (ick!). + // + // 09/05/2010: did some testing tonight, seems this isn't as big a deal (yet) + // as I thought it could be. Will keep my eye on the situation. + //// + + /** + * The decoration actor for tinytlf. + * + * @see org.tinytlf.decor.ITextDecoration + */ + public class TextDecor implements ITextDecor + { + public static const CARET_LAYER:int = 1; + public static const SELECTION_LAYER:int = 2; + + protected var _engine:ITextEngine; + + public function get engine():ITextEngine + { + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if(textEngine == _engine) + return; + + _engine = textEngine; + } + + public function render():void + { + for each(var el:Element in elements) + { + el.decorations.sort(function(d1:Decoration, d2:Decoration):int + { + return d1.layer - d2.layer; + }); + + for each(var dec:Decoration in el.decorations) + { + dec.draw(dec.setup(el.element)); + } + } + } + + public function removeAll():void + { + for each(var el:Element in elements) + { + for each(var dec:Decoration in el.decorations) + { + el.removeDecoration(dec); + dec.destroy(); + } + } + + elements.length = 0; + } + + private var elements:Vector. = new Vector.(); + + public function decorate(element:*, styleObject:Object, layer:int = 2, + container:ITextContainer = null, foreground:Boolean = false):void + { + var styleProp:String = String(styleObject); + + // Early return optimizations not to loop or invalidate + // if there are no decorations in the styleObject. + if(styleObject is String && !hasDecoration(styleProp)) + return; + else + { + var hasOne:Boolean = false; + for(styleProp in styleObject) + { + if(hasOne = hasDecoration(styleProp)) + break; + } + + if(hasOne == false) + return; + } + + var el:Element = getElement(element); + + if(styleObject is String && hasDecoration(styleProp)) + { + el.addDecoration(new Decoration(getDecoration(styleProp, container), layer)); + } + else + { + var decoration:ITextDecoration; + var dec:Decoration; + var styleValue:*; + + for(styleProp in styleObject) + { + styleValue = styleObject[styleProp]; + //You can de-apply a decoration by passing in {decoration:false} or {decoration:null} + if(styleValue === false || styleValue === 'false' || styleValue == null) + { + undecorate(element, styleProp); + } + else if(hasDecoration(styleProp)) + { + dec = el.getDecoration(decorationsMap[styleProp]); + if(dec) + { + decoration = dec.decoration; + decoration.mergeWith(styleObject); + continue; + } + + decoration = getDecoration(styleProp, container); + decoration.foreground = foreground; + decoration.style = styleObject; + el.addDecoration(new Decoration(decoration, layer)); + } + } + } + + engine.invalidateDecorations(); + } + + public function undecorate(element:* = null, decorationProp:String = null):void + { + if(element === null && decorationProp === null) + return; + + var el:Element; + var dec:Decoration; + + if(element) + { + el = getElement(element); + + if(!el) + return; + + if(decorationProp) + { + if(!hasDecoration(decorationProp)) + return; + + dec = el.getDecoration(decorationsMap[decorationProp]); + while(dec) + { + el.removeDecoration(dec); + dec.destroy(); + dec = el.getDecoration(decorationsMap[decorationProp]); + } + } + else + { + var decorations:Vector. = el.decorations.concat(); + for each(dec in decorations) + { + el.removeDecoration(dec); + dec.destroy(); + } + } + + cleanupElement(el); + } + else + { + if(!hasDecoration(decorationProp)) + return; + + var type:Class = decorationsMap[decorationProp]; + var ements:Vector. = elements.concat(); + + for each(el in ements) + { + dec = el.getDecoration(type); + if(dec) + { + el.removeDecoration(dec); + dec.destroy(); + cleanupElement(el); + } + } + } + + engine.invalidateDecorations(); + } + + protected var decorationsMap:Object = {}; + + public function mapDecoration(styleProp:String, decorationClassOrFactory:Object):void + { + if(decorationClassOrFactory) + decorationsMap[styleProp] = decorationClassOrFactory; + else + unMapDecoration(styleProp); + } + + public function unMapDecoration(styleProp:String):Boolean + { + if(!hasDecoration(styleProp)) + return false; + + return delete decorationsMap[styleProp]; + } + + public function hasDecoration(decorationProp:String):Boolean + { + return Boolean(decorationProp in decorationsMap); + } + + public function getDecoration(styleProp:String, container:ITextContainer = null):ITextDecoration + { + if(!hasDecoration(styleProp)) + return null; + + var decoration:* = decorationsMap[styleProp]; + if(decoration is Class) + decoration = ITextDecoration(new decoration()); + else if(decoration is Function) + decoration = ITextDecoration((decoration as Function)()); + + if(!decoration) + return null; + + ITextDecoration(decoration).container = container; + + ITextDecoration(decoration).engine = engine; + + return ITextDecoration(decoration); + } + + protected function getElement(element:*):Element + { + var filter:Vector. = elements.filter(function(e:Element, ... args):Boolean + { + return e.element === element; + }); + + if(filter.length) + return filter[0]; + + var el:Element = new Element(element); + elements.push(el); + return el; + } + + protected function cleanupElement(el:Element):void + { + if(el.decorations.length > 0) + return; + + elements.splice(elements.indexOf(el), 1); + el.decorations = null; + el.element = null; + } + } +} + +import flash.events.EventDispatcher; +import flash.geom.Rectangle; +import flash.text.engine.ContentElement; + +import org.tinytlf.decor.ITextDecoration; + +internal class Element +{ + public function Element(e:*) + { + this.element = e; + + if(e is ContentElement && ContentElement(e).eventMirror == null) + ContentElement(e).eventMirror = new EventDispatcher(); + } + + public var element:*; + public var decorations:Vector. = new Vector.(); + + public function getDecoration(decorationType:Class):Decoration + { + var filter:Vector. = decorations.filter(function(d:Decoration, ... args):Boolean + { + return (d.decoration is decorationType); + }); + + return filter.length ? filter[0] : null; + } + + public function addDecoration(decoration:Decoration):void + { + if(decorations.indexOf(decoration) == -1) + decorations.push(decoration); + } + + public function removeDecoration(decoration:Decoration):void + { + var i:int = decorations.indexOf(decoration); + if(i != -1) + decorations.splice(i, 1); + } +} + +internal class Decoration +{ + public function Decoration(decoration:ITextDecoration, layer:int = 2) + { + this.decoration = decoration; + this.layer = layer; + } + + public function destroy():void + { + decoration.destroy(); + decoration = null; + } + + public function setup(... args):Vector. + { + return decoration.setup.apply(null, [layer].concat(args)); + } + + public function draw(bounds:Vector.):void + { + decoration.draw(bounds); + } + + public var layer:int = 2; + public var decoration:ITextDecoration; +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/decor/TextDecoration.as b/tinytlf-core/src/org/tinytlf/decor/TextDecoration.as new file mode 100755 index 0000000..02fc410 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/decor/TextDecoration.as @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.decor +{ + import flash.display.*; + import flash.events.*; + import flash.geom.*; + import flash.text.engine.*; + import flash.utils.*; + + import org.tinytlf.ITextEngine; + import org.tinytlf.layout.ITextContainer; + import org.tinytlf.styles.StyleAwareActor; + import org.tinytlf.util.fte.ContentElementUtil; + + public class TextDecoration extends StyleAwareActor implements ITextDecoration + { + public function TextDecoration(styleObject:Object = null) + { + super(styleObject); + } + private var _container:ITextContainer; + + public function get container():ITextContainer + { + return _container; + } + + public function set container(textContainer:ITextContainer):void + { + if(textContainer === _container) + return; + + _container = textContainer; + } + + protected var _engine:ITextEngine; + + public function get engine():ITextEngine + { + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if(textEngine == _engine) + return; + + _engine = textEngine; + } + + private var _foreground:Boolean = false; + + public function set foreground(value:Boolean):void + { + _foreground = value; + } + + public function get foreground():Boolean + { + return _foreground; + } + + protected var rectToContainer:Dictionary = new Dictionary(true); + + public function setup(layer:int = 2, ... args):Vector. + { + var bounds:Vector. = new []; + + if(args.length <= 0) + return bounds; + + var arg:* = args[0]; + var rect:Rectangle; + + var tl:TextLine; + + if(arg is ContentElement) + { + //// + // When you decorate a ContentElement, there's no way to + // associate it with the ITextContainer(s) that it renders in. + // Here, grab every container that holds TextLines for the + // element. + // + // @TODO + // It might be better if this exists somewhere else. + // Theoretically the ITextLayout's layout routine could update + // ITextDecor's decorations that are keyed off ContentElements + // as the lines are rendering, but I'm almost certain this would + // require special API to stay efficient. + //// + + var tlmrs:Vector. = ContentElementUtil.getMirrorRegions(ContentElement(arg)); + var tlmr:TextLineMirrorRegion; + + var n:int = tlmrs.length; + for(var i:int = 0; i < n; ++i) + { + tlmr = tlmrs[i]; + rect = tlmr.bounds.clone(); + tl = tlmr.textLine; + rect.offset(tl.x, tl.y); + rectToContainer[rect] = ensureLayerExists(engine.layout.getContainerForLine(tl), layer) + bounds.push(rect); + } + } + else if(arg is TextBlock) + { + var block:TextBlock = TextBlock(arg); + tl = block.firstLine; + rect = tl.getBounds(tl.parent); + var tc:ITextContainer = engine.layout.getContainerForLine(tl); + + while(tl) + { + if(tc != engine.layout.getContainerForLine(tl)) + { + rectToContainer[rect] = ensureLayerExists(tc, layer); + bounds.push(rect); + rect = tl.getBounds(tl.parent) + } + + rect = rect.union(tl.getBounds(tl.parent)); + tc = engine.layout.getContainerForLine(tl); + tl = tl.nextLine; + + if(!tl) + { + rectToContainer[rect] = ensureLayerExists(tc, layer); + bounds.push(rect); + } + } + } + else + { + if(arg is TextLine) + { + tl = TextLine(arg); + bounds.push(tl.getBounds(tl.parent)); + } + else if(arg is Rectangle) + { + bounds.push(arg); + } + else if(arg is Vector.) + { + bounds = bounds.concat(arg); + } + else if(arg is Point) + { + bounds.push(new Rectangle(arg.x, arg.y)); + } + + var sprite:Sprite = ensureLayerExists(container, layer); + for each(rect in bounds) + { + rectToContainer[rect] = sprite; + } + } + + return bounds; + } + + public function draw(bounds:Vector.):void + { + if(!container) + return; + } + + public function destroy():void + { + _container = null; + _engine = null; + rectToContainer = null; + } + + protected function ensureLayerExists(container:ITextContainer, layer:int):Sprite + { + if(container == null) + return null; + + var shapes:Sprite = foreground ? container.foreground : container.background; + while(shapes.numChildren < (layer + 1)) + { + shapes.addChild(new MouseDisabledSprite()); + } + + return Sprite(shapes.getChildAt(shapes.numChildren - layer - 1)); + } + + protected function rectToLayer(rect:Rectangle):Sprite + { + return rectToContainer[rect] || (container ? container.foreground : null); + } + } +} +import flash.display.Sprite; + +internal class MouseDisabledSprite extends Sprite +{ + public function MouseDisabledSprite() + { + mouseEnabled = false; + mouseChildren = true; + } +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/interaction/EventLineInfo.as b/tinytlf-core/src/org/tinytlf/interaction/EventLineInfo.as new file mode 100755 index 0000000..110b3d5 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/interaction/EventLineInfo.as @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.interaction +{ + import flash.display.Sprite; + import flash.events.*; + import flash.text.engine.*; + + import org.tinytlf.ITextEngine; + import org.tinytlf.layout.ITextContainer; + + // This code is entirely too clever to be proud of it. + // Seriously, I'm a fucking douche. + public class EventLineInfo + { + private static function eventFilter(event:Event):Function + { + return (event is MouseEvent) ? mouseTargetFilter : genericTargetFilter; + } + + private static function mouseTargetFilter(target:*):Info + { + if(target is ITextContainer) + return new MouseContainerInfo(); + if(target is TextLine) + return new MouseLineInfo(); + if(target is Sprite) + return new MouseSpriteInfo(); + + return new NullInfo(); + } + + private static function genericTargetFilter(target:*):Info + { + if(target is ITextContainer) + return new GenericContainerInfo(); + if(target is TextLine) + return new GenericLineInfo(); + if(target is Sprite) + return new GenericSpriteInfo(); + + return new NullInfo(); + } + + public static function getInfo(event:Event):EventLineInfo + { + return Info(eventFilter(event).call(null, event.target)) + .getInfo(event, event.target) + || + Info(eventFilter(event).call(null, event.currentTarget)) + .getInfo(event, event.currentTarget); + } + + public function EventLineInfo(line:TextLine, engine:ITextEngine, + mirrorRegion:TextLineMirrorRegion, element:ContentElement) + { + _engine = engine; + _element = element; + _line = line; + _mirrorRegion = mirrorRegion; + _container = engine.layout.getContainerForLine(line); + } + + private var _container:ITextContainer; + public function get container():ITextContainer + { + return _container; + } + + private var _engine:ITextEngine; + public function get engine():ITextEngine + { + return _engine; + } + + private var _element:ContentElement; + public function get element():ContentElement + { + return _element; + } + + private var _line:TextLine; + public function get line():TextLine + { + return _line; + } + + private var _mirrorRegion:TextLineMirrorRegion; + public function get mirrorRegion():TextLineMirrorRegion + { + return _mirrorRegion; + } + } +} +import flash.display.*; +import flash.events.*; +import flash.geom.*; +import flash.text.engine.*; + +import org.tinytlf.*; +import org.tinytlf.interaction.EventLineInfo; +import org.tinytlf.layout.ITextContainer; +import org.tinytlf.util.TinytlfUtil; +import org.tinytlf.util.fte.TextLineUtil; + +internal interface Info +{ + function getInfo(event:Event, target:*):EventLineInfo; +} + +internal class NullInfo implements Info +{ + public function getInfo(event:Event, target:*):EventLineInfo + { + return new EventLineInfo(null, null, null, null); + } +} + +internal class MouseContainerInfo implements Info +{ + public function getInfo(event:Event, target:*):EventLineInfo + { + var m:MouseEvent = MouseEvent(event); + var c:ITextContainer = ITextContainer(target); + var engine:ITextEngine = c.engine; + var p:Point = new Point(m.stageX, m.stageY); + + var globalIndex:int = TinytlfUtil.pointToGlobalIndex(engine, p); + var line:TextLine = TinytlfUtil.globalIndexToTextLine(engine, globalIndex); + var atomIndex:int = TextLineUtil.getAtomIndexAtPoint(line, p) + var element:ContentElement = TextLineUtil.getElementAtAtomIndex(line, atomIndex); + var mirrorRegion:TextLineMirrorRegion = TextLineUtil.getMirrorRegionForElement(line, element); + + return new EventLineInfo(line, engine, mirrorRegion, element); + } +} + +internal class GenericContainerInfo implements Info +{ + public function getInfo(event:Event, target:*):EventLineInfo + { + var c:ITextContainer = ITextContainer(target); + var engine:ITextEngine = c.engine; + + var globalIndex:int = engine.caretIndex; + + var line:TextLine = TinytlfUtil.globalIndexToTextLine(engine, globalIndex); + var atomIndex:int = TinytlfUtil.globalIndexToAtomIndex(engine, line, engine.caretIndex); + var element:ContentElement = TextLineUtil.getElementAtAtomIndex(line, atomIndex); + var mirrorRegion:TextLineMirrorRegion = TextLineUtil.getMirrorRegionForElement(line, element); + + return new EventLineInfo(line, engine, mirrorRegion, element); + } +} + +internal class MouseLineInfo implements Info +{ + public function getInfo(event:Event, target:*):EventLineInfo + { + var line:TextLine = TextLine(target); + + if(line.validity != TextLineValidity.VALID) + { + trace('error: TextLine validity was ' + line.validity); + return null; + } + + var engine:ITextEngine = line.userData as ITextEngine; + var block:TextBlock = line.textBlock; + var atomIndex:int = 0; + var m:MouseEvent = MouseEvent(event); + + atomIndex = TextLineUtil.getAtomIndexAtPoint(line, new Point(m.stageX, m.stageY)); + var element:ContentElement = TextLineUtil.getElementAtAtomIndex(line, atomIndex); + var mirrorRegion:TextLineMirrorRegion = TextLineUtil.getMirrorRegionForElement(line, element); + + return new EventLineInfo(line, engine, mirrorRegion, element); + } +} + +internal class GenericLineInfo implements Info +{ + public function getInfo(event:Event, target:*):EventLineInfo + { + var line:TextLine = TextLine(target); + + if(line.validity != TextLineValidity.VALID) + { + trace('error: TextLine validity was ' + line.validity); + return null; + } + + var engine:ITextEngine = line.userData as ITextEngine; + var block:TextBlock = line.textBlock; + var atomIndex:int = 0; + + atomIndex = TinytlfUtil.globalIndexToAtomIndex(engine, line, engine.caretIndex); + var element:ContentElement = TextLineUtil.getElementAtAtomIndex(line, atomIndex); + var mirrorRegion:TextLineMirrorRegion = TextLineUtil.getMirrorRegionForElement(line, element); + + return new EventLineInfo(line, engine, mirrorRegion, element); + } +} + +internal class MouseSpriteInfo implements Info +{ + public function getInfo(event:Event, target:*):EventLineInfo + { + var m:MouseEvent = event as MouseEvent; + var sprite:Sprite = Sprite(target); + + var objs:Array = sprite.getObjectsUnderPoint(new Point(m.localX, m.localY)); + + var line:TextLine; + + while(line == null && objs.length) + line = objs.shift() as TextLine; + + if(!line) + return null; + + return event is MouseEvent ? + new MouseLineInfo().getInfo(event, line) : + new GenericLineInfo().getInfo(event, line); + } +} + +internal class GenericSpriteInfo implements Info +{ + public function getInfo(event:Event, target:*):EventLineInfo + { + throw new Error("How am I supposed to know which line to select when all " + + "you give me is a Sprite with no coordinates?"); + } +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/interaction/ITextInteractor.as b/tinytlf-core/src/org/tinytlf/interaction/ITextInteractor.as new file mode 100755 index 0000000..150f8d4 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/interaction/ITextInteractor.as @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.interaction +{ + import flash.events.EventDispatcher; + import flash.events.IEventDispatcher; + + import org.tinytlf.ITextEngine; + + /** + *

    + * ITextInteractor is the central interaction actor for tinytlf. You can + * externally map element names or types to Classes or Functions which + * return EventDispatcher instances. + *

    + *

    + * This map is most commonly called during ContentElement + * creation, from IContentElementFactorys to provide an + * eventMirror EventDispatcher to the + * ContentElement. + *

    + * + * @see org.tinytlf.layout.model.factories.IContentElementFactory + */ + public interface ITextInteractor extends IEventDispatcher + { + /** + *

    + * Reference to the central ITextEngine facade for this + * interactor. + *

    + * + * @see org.tinytlf.ITextEngine + */ + function get engine():ITextEngine; + function set engine(textEngine:ITextEngine):void; + + /** + *

    + * Maps a mirror Class or Function to an elementName. + *

    + * + * @return True if the mirror was successfully mapped, False + * if it wasn't. + */ + function mapMirror(element:*, mirrorClassOrFactory:Object):void; + + /** + *

    + * Unmaps the mirror Class or Function for the given element. + *

    + * + * @return True if the mirror was successfully unmapped, or False + * if it wasn't. + */ + function unMapMirror(element:*):Boolean; + + /** + *

    + * Checks to see if this interactor has a Class or Function for the + * given element. + *

    + * + * @return True if there is a mirror, False if there isn't. + */ + function hasMirror(element:*):Boolean; + + /** + *

    + * Returns an EventDispatcher for an element name. Typically an + * IContentElementFactory calls this when it creates a ContentElement + * and needs to specify an eventMirror. + *

    + *

    + * If a Class was mapped, this instantiates the class and returns an + * instance. If a Function was mapped, this returns the value returned + * by calling the Function. + *

    + *

    + * If element is null, this method should return a generic + * EventDispatcher instance. + *

    + */ + function getMirror(element:* = null):EventDispatcher; + } +} + diff --git a/src/org/tinytlf/interaction/TextInteractorBase.as b/tinytlf-core/src/org/tinytlf/interaction/TextInteractorBase.as old mode 100644 new mode 100755 similarity index 58% rename from src/org/tinytlf/interaction/TextInteractorBase.as rename to tinytlf-core/src/org/tinytlf/interaction/TextInteractorBase.as index 071417c..6140963 --- a/src/org/tinytlf/interaction/TextInteractorBase.as +++ b/tinytlf-core/src/org/tinytlf/interaction/TextInteractorBase.as @@ -6,11 +6,12 @@ */ package org.tinytlf.interaction { - import org.tinytlf.ITextEngine; - import flash.events.EventDispatcher; + import flash.utils.Dictionary; + + import org.tinytlf.ITextEngine; - public class TextInteractorBase extends TextDispatcherBase implements ITextInteractor + public class TextInteractorBase extends EventDispatcher implements ITextInteractor { protected var _engine:ITextEngine; public function get engine():ITextEngine @@ -26,34 +27,37 @@ package org.tinytlf.interaction _engine = textEngine; } - public function getMirror(elementName:String = ""):EventDispatcher + private var mirrorMap:Dictionary = new Dictionary(true); + + public function mapMirror(element:*, mirrorClassOrFactory:Object):void { - var mirror:Object = mirrorMap[elementName]; - - if(!mirror) - return this; - - if(mirror is Class) - return new(mirror as Class)() as EventDispatcher; - - if(mirror is Function) - return (mirror as Function)() as EventDispatcher; + mirrorMap[element] = mirrorClassOrFactory; + } + + public function unMapMirror(element:*):Boolean + { + if(element in mirrorMap) + return delete mirrorMap[element]; - return mirror as EventDispatcher; + return false; } - private var mirrorMap:Object = {}; - public function mapMirror(elementName:String, mirrorClassOrInstance:Object):void + public function hasMirror(element:*):Boolean { - mirrorMap[elementName] = mirrorClassOrInstance; + return Boolean(element in mirrorMap); } - public function unMapMirror(elementName:String):Boolean + public function getMirror(element:* = null):EventDispatcher { - if(elementName in mirrorMap) - return delete mirrorMap[elementName]; + var mirror:Object = mirrorMap[element]; - return false; + if(mirror is Class) + return new(mirror as Class)() as EventDispatcher; + + if(mirror is Function) + return (mirror as Function)() as EventDispatcher; + + return null; } } } diff --git a/tinytlf-core/src/org/tinytlf/layout/ITextContainer.as b/tinytlf-core/src/org/tinytlf/layout/ITextContainer.as new file mode 100755 index 0000000..3601e10 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/ITextContainer.as @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.layout +{ + import flash.display.*; + import flash.text.engine.*; + + import org.tinytlf.ITextEngine; + + /** + * ITextContainer is the layout controller for a + * DisplayObjectContainer used in text layout. Tinytlf renders TextLines + * across multiple DisplayObjectContainers, and it delegates control + * over layout and line creation to ITextContainers. + * + *

    For a DisplayObjectContainer to be used for layout, it must belong to + * an ITextContainer registered with the ITextLayout layout actor. + * ITextLayout handles rendering across containers. ITextContainer + * participates in layout by rendering as many lines as possible into its + * target DisplayObjectContainer, then returning either: + *

      + *
    • the last line successfully rendered, meaning there's no more room for + * lines in the target DisplayObjectContainer, or
    • + *
    • null, meaning all the lines from the TextBlock were + * rendered and there's still more space in the target + * DisplayObjectContainer.
    • + *
    + *

    + * + *

    ITextContainer exposes a shapes Sprite, used + * for drawing decorations into the target DisplayObjectContainer.

    + * + * @see org.tinytlf.layout.ITextLayout + */ + public interface ITextContainer + { + /** + * Reference to the central ITextEngine facade for this + * container. + * + * @see org.tinytlf.ITextEngine + */ + function get engine():ITextEngine; + function set engine(textEngine:ITextEngine):void; + + /** + * The target DisplayObjectContainer for this + * ITextContainer. TextLines are added to and + * removed from the target during layout. + * + * @see org.tinytlf.layout.ITextLayout + */ + function get target():Sprite; + function set target(textContainer:Sprite):void; + + /** + * The Sprite which background decorations are rendered into. Background + * decorations exist behind the TextLines. + * + * @see org.tinytlf.decor.ITextDecor + * @see org.tinytlf.decor.ITextDecoration + */ + function get background():Sprite; + function set background(shapesContainer:Sprite):void; + + /** + * The Sprite which foreground decorations are rendered into. Foreground + * decorations exist in front of the TextLines. + * + * @see org.tinytlf.decor.ITextDecor + * @see org.tinytlf.decor.ITextDecoration + */ + function get foreground():Sprite; + function set foreground(shapesContainer:Sprite):void; + + /** + * The Sprite in which TextLines are added. Exists between the + * background and foreground Sprites. + * + * @see org.tinytlf.decor.ITextDecor + * @see org.tinytlf.decor.ITextDecoration + */ + function get lines():Sprite; + function set lines(linesContainer:Sprite):void; + + /** + * The height within which to render and lay out TextLines. If this + * value is not set, the ITextContainer will render lines + * into this container indefinitely, never signaling that the container + * is full. + * + * @see scrollable + */ + function get explicitHeight():Number; + function set explicitHeight(value:Number):void; + + /** + * The defined width to render TextLines during layout. + * If this value is not set, lines are created with + * TextLine.MAX_LINE_WIDTH, 1000000. + * + */ + function get explicitWidth():Number; + function set explicitWidth(value:Number):void; + + /** + * The width of the widest TextLine in this + * ITextContainer. If the lines aren't justified, the Flash + * Text Engine will attempt to render lines into a certain width. If the + * FTE determines an atom or word won't fit, it will defer the atom or + * word to the next TextLine. The result is that the actual width of the + * rendered TextLine is less than the specifiedWidth. In turn, the + * measuredWidth of this TextContainer won't necessarily be + * the same as the explicitWidth. + * + */ + function get measuredWidth():Number; + function set measuredWidth(value:Number):void; + + /** + * The measured height of all the TextLines, including lineHeight and + * paddingTop/paddingBottom. + * + */ + function get measuredHeight():Number; + function set measuredHeight(value:Number):void; + + /** + * Whether the TextContainer is scrollable or not. The TextContainer must + * have an explicitHeight (or explicitWidth if + * a vertical layout is applied) defined which binds TextLines within + * the content area. + * + * @see explicitHeight + */ + function get scrollable():Boolean; + function set scrollable(value:Boolean):void; + + /** + * The total height of the measured lines in the content area. This is + * irrespective of scrolling, and is the height that should be used as + * the total height to calculate a scrollbar. + * + * @see measuredHeight + */ + function get totalHeight():Number; + function set totalHeight(value:Number):void; + + /** + * The total width of the measured lines in the content area. This is + * irrespective of scrolling, and is the width that should be used as + * the total width to calculate a scrollbar. + * + * @see measuredWidth + */ + function get totalWidth():Number; + function set totalWidth(value:Number):void; + + /** + * Checks whether this ITextContainer has a particular TextLine. + * + * @returns true if the line is in this TextContainer, false otherwise. + */ + function hasLine(line:TextLine):Boolean; + + /** + * Renders as many TextLines from the specified + * TextBlock into the target as possible. + * + *

    There is a special contract between layout and + * ITextLayout#render.
    + * This method is passed a TextBlock to render into the + * target DisplayObjectContainer. If all the lines from the + * TextBlock were able to be rendered into the target, this method + * returns null, indicating that there is still space left in the + * target. If the TextLines went out of the targets boundaries, this + * method returns the last TextLine that fit, indicating that there is + * no more room in the target and the ITextLayout should move to the + * next container.

    + * + * @see org.tinytlf.layout.ITextLayout#render + */ + function layout(block:TextBlock, line:TextLine):TextLine; + + /** + * Called on every ITextContainer before layout begins. + */ + function preLayout():void; + + /** + * Called on every ITextContainer after the entire layout pass ends. + */ + function postLayout():void; + + /** + * Clears the graphics and removes all children from the + * shapes Sprite. + * + */ + function resetShapes():void; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/layout/ITextLayout.as b/tinytlf-core/src/org/tinytlf/layout/ITextLayout.as new file mode 100755 index 0000000..6f7dc8c --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/ITextLayout.as @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.layout +{ + import flash.text.engine.TextLine; + + import org.tinytlf.ITextEngine; + import org.tinytlf.analytics.IVirtualizer; + + /** + * ITextLayout is the tinytlf text layout actor. ITextLayout is responsible + * for: + *
      + *
    • Parsing the TextField data into Flash Text Engine ContentElements.
    • + *
    • Laying out a Vector of TextBlocks across multiple + * DisplayObjectContainers.
    • + *
    + * + *

    ITextLayout's textBlockFactory transforms the data into + * Flash Text Engine ContentElements and TextBlocks. This is the closest + * thing tinytlf has to a "Model" actor. The data can be in any format, as + * long as you can write a conversion or parsing routine to generate + * ContentElements and TextBlocks.

    + * + *

    ITextLayout maintains a list of ITextContainers, which are layout + * controllers that manage text layout inside DisplayObjectContainers.

    + * + * @see org.tinytlf.layout.model.factories.ILayoutFactoryMap + * @see org.tinytlf.layout.ITextContainer + */ + public interface ITextLayout + { + /** + * A read-only list of ITextContainers on this layout. To add and remove + * containers, use addContainer and + * removeContainer instead. + * + * @see org.tinytlf.layout.ITextContainer + */ + function get containers():Vector.; + + /** + *

    + * Reference to the central ITextEngine facade for this + * layout. + *

    + * + * @see org.tinytlf.ITextEngine + */ + function get engine():ITextEngine; + function set engine(textEngine:ITextEngine):void; + + /** + *

    + * Reference to the IVirtualizer this ITextLayout keeps for + * virtualizing TextBlocks. + *

    + */ + function get textBlockVirtualizer():IVirtualizer; + + /** + * Adds an ITextContainer to be managed and used in layout. + * + * @see org.tinytlf.layout.ITextContainer + */ + function addContainer(container:ITextContainer):void; + + /** + * Removes an ITextContainer from use in layout. + * + * @see org.tinytlf.layout.ITextContainer + */ + function removeContainer(container:ITextContainer):void; + + /** + * Returns the ITextContainer that a particular TextLine exists in. + * Returns null if no container was found to the line. + */ + function getContainerForLine(line:TextLine):ITextContainer; + + /** + * Clears the graphics context and removes all children of each shapes + * sprite on each ITextContainer. + * + * @see org.tinytlf.layout.ITextContainer#resetShapes() + */ + function resetShapes():void; + + /** + * Renders as many lines from the Vector of TextBlocks as possible. + * + *

    This method attempts to render as many TextLines from each + * TextBlock into the available ITextContainers as possible. A special + * contract exists between render and + * org.tinytlf.layout.ITextContainer#layout: #render() + * passes a TextBlock and the previously rendered line to + * layout, and layout returns either: + *

      + *
    • The last line rendered out of the TextBlock, which indicates that + * there is no more space in the ITextContainer, and this ITextLayout + * should move on to the next ITextContainer, keeping the same + * TextBlock, or
    • + *
    • null indicating that there are no more lines in the + * TextBlock but still more space in the ITextContainer, and this + * ITextLayout should move on to the next TextBlock, keeping the same + * ITextContainer.
    • + *
    + *

    + * + *

    This method quits either when all the lines have been rendered + * into the ITextContainers, or all the ITextContainers are full of + * TextLines.

    + * + * @see org.tinytlf.layout.ITextContainer#layout() + */ + function render():void; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/layout/TextContainerBase.as b/tinytlf-core/src/org/tinytlf/layout/TextContainerBase.as new file mode 100755 index 0000000..d1c645b --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/TextContainerBase.as @@ -0,0 +1,393 @@ +/* +* Copyright (c) 2010 the original author or authors +* +* Permission is hereby granted to use, modify, and distribute this file +* in accordance with the terms of the license agreement accompanying it. +*/ +package org.tinytlf.layout +{ + import flash.display.*; + import flash.text.engine.*; + import flash.utils.Dictionary; + + import org.tinytlf.ITextEngine; + + public class TextContainerBase implements ITextContainer + { + public function TextContainerBase(container:Sprite, + explicitWidth:Number = NaN, + explicitHeight:Number = NaN) + { + this.target = container; + + _explicitWidth = explicitWidth; + _explicitHeight = explicitHeight; + } + + public function layout(block:TextBlock, line:TextLine):TextLine + { + return null; + } + + protected function createTextLine(block:TextBlock, previousLine:TextLine):TextLine + { + return null; + } + + protected var _target:Sprite; + + public function get target():Sprite + { + return _target; + } + + public function set target(doc:Sprite):void + { + if(doc == _target) + return; + + _target = doc; + + background = Sprite(target.addChildAt(bgShapes || new Sprite(), 0)); + lines = Sprite(target.addChild(lines || new Sprite())); + foreground = Sprite(target.addChild(fgShapes || new Sprite())); + } + + protected var _engine:ITextEngine; + + public function get engine():ITextEngine + { + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if(textEngine == _engine) + return; + + _engine = textEngine; + } + + private var bgShapes:Sprite; + + public function get background():Sprite + { + return bgShapes; + } + + public function set background(shapesContainer:Sprite):void + { + if(shapesContainer === bgShapes) + return; + + bgShapes = shapesContainer; + } + + private var fgShapes:Sprite; + + public function get foreground():Sprite + { + return fgShapes; + } + + public function set foreground(shapesContainer:Sprite):void + { + if(shapesContainer === fgShapes) + return; + + fgShapes = shapesContainer; + + if(fgShapes) + { + // Don't let foreground shapes get in the way of interacting + // with the TextLines. + fgShapes.mouseEnabled = false; + //But maybe we do want children to receive mouse events? + //fgShapes.mouseChildren = false; + } + } + + private var lineContainer:Sprite; + + public function get lines():Sprite + { + return lineContainer; + } + + public function set lines(container:Sprite):void + { + if(container === lineContainer) + return; + + lineContainer = container; + + if(lineContainer) + { + lineContainer.mouseChildren = true; + lineContainer.mouseEnabled = false; + } + } + + protected var _explicitHeight:Number = NaN; + + public function get explicitHeight():Number + { + return _explicitHeight; + } + + public function set explicitHeight(value:Number):void + { + if(value === _explicitHeight) + return; + + _explicitHeight = value; + invalidateVisibleLines(); + engine.invalidate(); + } + + protected var _explicitWidth:Number = NaN; + + public function get explicitWidth():Number + { + return _explicitWidth; + } + + public function set explicitWidth(value:Number):void + { + if(value === _explicitWidth) + return; + + _explicitWidth = value; + invalidateVisibleLines(); + engine.invalidate(); + } + + protected var height:Number = 0; + + public function get measuredHeight():Number + { + return height; + } + + public function set measuredHeight(value:Number):void + { + if(value === height) + return; + + height = value; + } + + protected var width:Number = 0; + + public function get measuredWidth():Number + { + return width; + } + + public function set measuredWidth(value:Number):void + { + if(value === width) + return; + + width = value; + } + + protected var tHeight:Number = 0; + + public function get totalHeight():Number + { + return tHeight; + } + + public function set totalHeight(value:Number):void + { + if(value === tHeight) + return; + + tHeight = value; + } + + protected var tWidth:Number = 0; + + public function get totalWidth():Number + { + return tWidth; + } + + public function set totalWidth(value:Number):void + { + if(value === tWidth) + return; + + tWidth = value; + } + + private var _scrollable:Boolean = true; + public function get scrollable():Boolean + { + return _scrollable; + } + + public function set scrollable(value:Boolean):void + { + if(value == _scrollable) + return; + + _scrollable = value; + if(engine) + engine.invalidate(); + } + + protected var orphanLines:Vector. = new []; + protected var visibleLines:Vector. = new []; + + public function hasLine(line:TextLine):Boolean + { + return visibleLines.indexOf(line) != -1; + } + + public function preLayout():void + { + var lines:Vector. = visibleLines.concat(); + var n:int = lines.length; + var l:TextLine; + + // Parse through and look for invalid lines. + for(var i:int = 0; i < n; i += 1) + { + l = lines[i]; + + if(l.validity === TextLineValidity.VALID) + continue; + + orphanLines.unshift(l); + removeLineFromTarget(l); + } + } + + public function postLayout():void + { + var visibleBlocks:Dictionary = engine.layout.textBlockVirtualizer.items; + var n:int = visibleLines.length; + var line:TextLine; + + for(var i:int = 0; i < n; i += 1) + { + line = visibleLines[i]; + if(line.textBlock in visibleBlocks) + continue; + + orphanLines.push(line); + } + + n = orphanLines.length; + for(i = 0; i < n; i += 1) + { + line = orphanLines[i]; + unregisterLine(line); + removeLineFromTarget(line); + } + } + + public function resetShapes():void + { + clearShapeContainer(foreground); + clearShapeContainer(background); + } + + private function clearShapeContainer(container:Sprite):void + { + if(!container) + return; + + container.graphics.clear(); + var n:int = container.numChildren; + var child:DisplayObject; + + for(var i:int = 0; i < n; ++i) + { + child = container.getChildAt(i); + if(child is Shape) + { + Shape(child).graphics.clear(); + } + else if(child is Sprite) + { + Sprite(child).graphics.clear(); + while(Sprite(child).numChildren) + Sprite(child).removeChildAt(0); + } + + } + } + + protected function registerLine(line:TextLine):void + { + if(!hasLine(line)) + visibleLines.push(line); + + line.userData = engine; + engine.interactor.getMirror(line); + } + + protected function unregisterLine(line:TextLine):void + { + line.userData = null; + + var i:int = visibleLines.indexOf(line); + if(i != -1) + visibleLines.splice(i, 1); + } + + protected function addLineToTarget(line:TextLine, index:int = 0):TextLine + { + if(lines.contains(line)) + return line; + +// index ||= target.numChildren > 1 ? target.numChildren - 1 : 1; + + return TextLine(lines.addChildAt(line, index)); + } + + protected function removeLineFromTarget(line:TextLine):TextLine + { + if(!lines.contains(line)) + return line; + + return TextLine(lines.removeChild(line)); + } + + protected function getLineIndexFromTarget(line:TextLine):int + { + if(!target.contains(line)) + return -1; + + return target.getChildIndex(line); + } + + protected function invalidateVisibleLines():void + { + var n:int = visibleLines.length; + for(var i:int = 0; i < n; ++i) + { + visibleLines[i].validity = TextLineValidity.INVALID; + } + + totalHeight = 0; + totalWidth = 0; + } + + protected function getRecycledLine(previousLine:TextLine):TextLine + { + if(orphanLines.length == 0) + return null; + + var line:TextLine = previousLine; + + while(line === previousLine) + line = orphanLines.pop(); + + return line; + } + } +} diff --git a/tinytlf-core/src/org/tinytlf/layout/TextLayoutBase.as b/tinytlf-core/src/org/tinytlf/layout/TextLayoutBase.as new file mode 100755 index 0000000..b85dab8 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/TextLayoutBase.as @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.layout +{ + import flash.text.engine.*; + + import org.tinytlf.*; + import org.tinytlf.analytics.*; + import org.tinytlf.conversion.*; + import org.tinytlf.layout.properties.*; + import org.tinytlf.util.*; + import org.tinytlf.util.fte.*; + + public class TextLayoutBase implements ITextLayout + { + protected var _engine:ITextEngine; + + public function get engine():ITextEngine + { + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if(textEngine == _engine) + return; + + _engine = textEngine; + } + + private const virtualizer:IVirtualizer = new Virtualizer(); + + public function get textBlockVirtualizer():IVirtualizer + { + return virtualizer; + } + + /** + * Clears the shapes out of this Layout's ITextContainers. + */ + public function resetShapes():void + { + for(var i:int = 0; i < containers.length; i++) + { + containers[i].resetShapes(); + } + } + + protected var _containers:Vector. = new Vector.; + + public function get containers():Vector. + { + return _containers ? _containers.concat() : new Vector.; + } + + public function addContainer(container:ITextContainer):void + { + if(containers.indexOf(container) != -1) + return; + + _containers.forEach(function(c:ITextContainer, ...args):void{ + c.scrollable = false; + }); + + _containers.push(container); + container.engine = engine; + container.scrollable = true; + engine.interactor.getMirror(container); + } + + public function removeContainer(container:ITextContainer):void + { + var i:int = containers.indexOf(container); + if(i == -1) + return; + + _containers.splice(i, 1); + container.engine = null; + } + + public function getContainerForLine(line:TextLine):ITextContainer + { + var n:int = containers.length; + + for(var i:int = 0; i < n; i++) + { + if(containers[i].hasLine(line)) + { + return containers[i]; + } + } + + return null; + } + + /** + * Renders all the TextLines from the list of TextBlocks into this + * layout's ITextContainers. Stops when it runs out of TextBlocks or + * runs out of space. + */ + public function render():void + { + if(!containers || !containers.length) + return; + + containers.forEach(function(c:ITextContainer, ... args):void{ + c.preLayout(); + }); + + var factory:ITextBlockFactory = engine.blockFactory; + + var blockIndex:int = textBlockVirtualizer.getIndexFromPosition(engine.scrollPosition); + blockIndex = blockIndex <= 0 ? 0 : blockIndex; + + var block:TextBlock = factory.getTextBlock(blockIndex); + var container:ITextContainer = containers[0]; + + beginRender(blockIndex); + + while(block && container) + { + container = renderBlockAcrossContainers(block, container); + + cacheBlock(block, blockIndex); + + block.releaseLineCreationData(); + + // Only get the next block if there's a container. + if(container) + { + block = factory.getTextBlock(++blockIndex); + } + } + + endRender(blockIndex); + + containers.forEach(function(c:ITextContainer, ... args):void{ + c.postLayout(); + }); + } + + /** + * Renders all the lines from the input TextBlock into the containers, + * starting from the container specified by startContainer. + * + * This method should render every line from the TextBlock into the + * ITextContainers. + * + * @returns The last ITextContainer rendered into, or null if there are + * no containers left. + */ + protected function renderBlockAcrossContainers(block:TextBlock, + startContainer:ITextContainer):ITextContainer + { + // If it's an invalid block that's already been rendered, re-render + // the damaged lines across multiple containers + if(TextBlockUtil.isInvalid(block) && block.firstLine) + return renderInvalidLines(block, startContainer); + + // If it's never been rendered, render all the lines across the + // containers. + // + // Or if the block is 100% valid, still call the ITC's layout + // method, but it should be smart enough to skip lines from this + // textblock. + + return renderAllLines(block, startContainer); + } + + protected function renderInvalidLines(block:TextBlock, container:ITextContainer):ITextContainer + { + // Check if there's any lines we need to render at the end + // of the TextBlock. + // Note: not sure if this case actually happens... + if(block.firstLine && !block.firstInvalidLine) + { + return renderLines(block, block.lastLine, container); + } + // Otherwise re-render the invalid lines. + else if(block.firstInvalidLine) + { + return renderLines(block, block.firstInvalidLine.previousLine, container); + } + + return container; + } + + protected function renderAllLines(block:TextBlock, container:ITextContainer):ITextContainer + { + return renderLines(block, null, container); + } + + protected function renderLines(block:TextBlock, + previousLine:TextLine, + container:ITextContainer):ITextContainer + { + var containerIndex:int = containers.indexOf(container); + var line:TextLine = container.layout(block, previousLine); + + while(line) + { + if(++containerIndex < containers.length) + container = containers[containerIndex]; + else + return null; + + line = container.layout(block, line); + } + + return container; + } + + protected function beginRender(startIndex:int):void + { + //Uncache the TextBlocks that exist before the startIndex + for(var i:int = 0; i < startIndex; i += 1) + { + TextBlockUtil.checkIn(textBlockVirtualizer.getItemFromIndex(i)); + textBlockVirtualizer.dequeueAt(i); + } + } + + protected function cacheBlock(block:TextBlock, index:int):void + { + var lp:LayoutProperties = TinytlfUtil.getLP(block); + var size:Number = lp.paddingTop + lp.height + lp.paddingBottom; + + textBlockVirtualizer.enqueueAt(block, index, size); + } + + protected function endRender(lastIndex:int):void + { + //Uncache any blocks after the end index. + for(var n:int = engine.blockFactory.numBlocks; lastIndex < n; lastIndex += 1) + { + TextBlockUtil.checkIn(textBlockVirtualizer.getItemFromIndex(lastIndex)); + textBlockVirtualizer.dequeueAt(lastIndex); + } + } + } +} + diff --git a/tinytlf-core/src/org/tinytlf/layout/properties/LayoutProperties.as b/tinytlf-core/src/org/tinytlf/layout/properties/LayoutProperties.as new file mode 100755 index 0000000..1331a2e --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/properties/LayoutProperties.as @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.layout.properties +{ + import flash.text.engine.*; + + import org.tinytlf.styles.StyleAwareActor; + + /** + * This class is essentially a struct which stores values that describe the + * layout values that should be applied to lines rendered from a TextBlock. + * + * This is associated via the TextBlock's userData value, and + * is the only valid value for the userData of a TextBlock in + * tinytlf. + * + * LayoutProperties is dynamic and extends from the tinytlf styling + * framework, so he's not without extension points. Most of the inline and + * block level layout values are defined as public members, but feel free to + * tack on properties as you need. + */ + public dynamic class LayoutProperties extends StyleAwareActor + { + public function LayoutProperties(props:Object = null) + { + super(props); + } + + public var x:Number = 0; + public var y:Number = 0; + + public var width:Number = 0; + public var height:Number = 0; + public var leading:Number = 0; + public var textIndent:Number = 0; + public var paddingLeft:Number = 0; + public var paddingRight:Number = 0; + public var paddingBottom:Number = 0; + public var paddingTop:Number = 0; + + public var textAlign:String = TextAlign.LEFT; + public var textDirection:String = TextDirection.LTR; + public var textTransform:String = TextTransform.NONE; + public var float:String = ''; + public var display:String = TextDisplay.INLINE; + public var letterSpacing:Boolean = false; + public var locale:String = 'en'; + + override protected function applyProperty(property:String, destination:Object):void + { + if(property === "textAlign" && destination is TextBlock) + { + setupBlockJustifier(TextBlock(destination)); + return; + } + + super.applyProperty(property, destination); + } + + /** + * Utility method which applies justification properties to the + * TextBlock before it's rendered. + */ + protected function setupBlockJustifier(block:TextBlock):void + { + var justification:String = LineJustification.UNJUSTIFIED; + var justifier:TextJustifier = TextJustifier.getJustifierForLocale(locale); + + if(textAlign == TextAlign.JUSTIFY) + justification = LineJustification.ALL_BUT_LAST; + + justifier.lineJustification = justification; + + if( !block.textJustifier || + block.textJustifier.lineJustification != justification || + block.textJustifier.locale != locale) + { + applyTo(justifier); + block.textJustifier = justifier; + } + } + } +} + diff --git a/tinytlf-core/src/org/tinytlf/layout/properties/TextAlign.as b/tinytlf-core/src/org/tinytlf/layout/properties/TextAlign.as new file mode 100755 index 0000000..c2d75ed --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/properties/TextAlign.as @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.layout.properties +{ + public final class TextAlign + { + public static const LEFT:String = 'left'; + public static const CENTER:String = 'center'; + public static const RIGHT:String = 'right'; + public static const JUSTIFY:String = 'justify'; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/layout/properties/TextDirection.as b/tinytlf-core/src/org/tinytlf/layout/properties/TextDirection.as new file mode 100755 index 0000000..2c3a5c7 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/properties/TextDirection.as @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.layout.properties +{ + public class TextDirection + { + public static const LTR:String = "ltr"; + public static const RTL:String = "rtl"; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/layout/properties/TextDisplay.as b/tinytlf-core/src/org/tinytlf/layout/properties/TextDisplay.as new file mode 100644 index 0000000..79ffcda --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/properties/TextDisplay.as @@ -0,0 +1,8 @@ +package org.tinytlf.layout.properties +{ + public final class TextDisplay + { + public static const INLINE:String = 'inline'; + public static const BLOCK:String = 'block'; + } +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/layout/properties/TextFloat.as b/tinytlf-core/src/org/tinytlf/layout/properties/TextFloat.as new file mode 100755 index 0000000..7a0fcf9 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/properties/TextFloat.as @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.layout.properties +{ + public final class TextFloat + { + public static const LEFT:String = 'left'; + public static const RIGHT:String = 'right'; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/layout/properties/TextTransform.as b/tinytlf-core/src/org/tinytlf/layout/properties/TextTransform.as new file mode 100755 index 0000000..9f38593 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/layout/properties/TextTransform.as @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.layout.properties +{ + public final class TextTransform + { + public static const CAPITALIZE:String = 'capitalize'; + public static const UPPERCASE:String = 'uppercase'; + public static const LOWERCASE:String = 'lowercase'; + public static const NONE:String = 'none'; + } +} + diff --git a/tinytlf-core/src/org/tinytlf/styles/IStyleAware.as b/tinytlf-core/src/org/tinytlf/styles/IStyleAware.as new file mode 100755 index 0000000..e94cdf8 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/styles/IStyleAware.as @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.styles +{ + /** + * An interface for an object that has styles. + */ + public interface IStyleAware + { + /** + * The style property of the IStyleAware implementation. Often times + * this object stores the key/value style pairs, but can also support + * wholesale style application and overloading. + */ + function get style():Object; + function set style(value:Object):void; + + /** + * Clears a style's definition and value from this implementation. + */ + function clearStyle(styleProp:String):Boolean; + + /** + * Gets the value for a style property. Can return null if this object's + * styles aren't set up, or undefined if the styleProp isn't a style on + * this instance. + */ + function getStyle(styleProp:String):*; + + /** + * Sets a styleProp to the value defined by newValue. + */ + function setStyle(styleProp:String, newValue:*):void; + + /** + * Applies this IStyleAware's styles to the specified Object. + */ + function applyTo(target:Object):void; + + /** + * Unapplies this IStyleAware's styles from the specified Object. + */ + function unapplyTo(target:Object):void; + + /** + * Merges the specified object's properties into this IStyleAware object. + */ + function mergeWith(withObj:Object):void; + + /** + * Removes the specified object's properties from this IStyleAware object. + */ + function unmergeWith(withObj:Object):void; + + /** + * toString of course. duh. + */ + function toString():String; + } +} + diff --git a/src/org/tinytlf/styles/ITextStyler.as b/tinytlf-core/src/org/tinytlf/styles/ITextStyler.as old mode 100644 new mode 100755 similarity index 79% rename from src/org/tinytlf/styles/ITextStyler.as rename to tinytlf-core/src/org/tinytlf/styles/ITextStyler.as index e276ab0..f81682d --- a/src/org/tinytlf/styles/ITextStyler.as +++ b/tinytlf-core/src/org/tinytlf/styles/ITextStyler.as @@ -9,7 +9,6 @@ package org.tinytlf.styles import flash.text.engine.ElementFormat; import org.tinytlf.ITextEngine; - import org.tinytlf.core.IStyleAware; public interface ITextStyler extends IStyleAware { @@ -21,7 +20,11 @@ package org.tinytlf.styles */ function getElementFormat(element:*):ElementFormat; - function getMappedStyle(element:*):*; + /** + * Creates a decorations object for an element which + * you can pass to the ITextDecor#decorate method. + */ + function describeElement(element:*):Object; function mapStyle(element:*, value:*):void; function unMapStyle(element:*):Boolean; diff --git a/tinytlf-core/src/org/tinytlf/styles/StyleAwareActor.as b/tinytlf-core/src/org/tinytlf/styles/StyleAwareActor.as new file mode 100755 index 0000000..ca027d3 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/styles/StyleAwareActor.as @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.styles +{ + import flash.utils.Proxy; + import flash.utils.flash_proxy; + + import org.tinytlf.util.TinytlfUtil; + + use namespace flash_proxy; + + /** + * StyleAwareActor is a useful base class for objects with sealed properties + * but who also wish to dynamically accept and store named values. + * + * Since it extends Proxy, it overrides the flash_proxy functions for setting + * and retrieving data. If you are calling a sealed property on + * StyleAwareActor or one of his subclasses, the property or function is called + * like normal. However, if you dynamically set or call a property on it, + * getStyle and setStyle are called instead. + * + * StyleAwareActor has a style member, on which the style + * properties and values are stored. You can pass in your own dynamic + * instance to store styles on by setting the style setter. + * This will set the new value as the internal styles storage object, as + * well as copy over all the key/value pairs currently on the new instance. + * + * This is useful if you wish to proxy styles, or to support external styling + * implementations (currently Flex and F*CSS). + */ + public dynamic class StyleAwareActor extends Proxy implements IStyleAware + { + public function StyleAwareActor(styleObject:Object = null) + { + if(!styleObject) + return; + + style = styleObject; + } + + protected var properties:Object = {}; + protected var propNames:Array = []; + + public function get style():Object + { + return properties; + } + + public function set style(value:Object):void + { + if(value === properties) + return; + + mergeWith(value); + + if(value is IStyleAware) + { + IStyleAware(value).mergeWith(this); + properties = value; + } + } + + public function clearStyle(styleProp:String):Boolean + { + return delete this[styleProp]; + } + + public function getStyle(styleProp:String):* + { + return this[styleProp]; + } + + public function setStyle(styleProp:String, newValue:*):void + { + this[styleProp] = newValue; + } + + public function mergeWith(object:Object):void + { + for(var prop:String in object) + mergeProperty(prop, object); + } + + protected function mergeProperty(property:String, source:Object):void + { + this[property] = source[property]; + } + + public function unmergeWith(object:Object):void + { + for(var prop:String in object) + unmergeProperty(prop); + } + + protected function unmergeProperty(property:String):void + { + delete this[property]; + } + + public function applyTo(object:Object):void + { + for(var prop:String in properties) + applyProperty(prop, object); + } + + protected function applyProperty(property:String, destination:Object):void + { + if(property in destination && !(destination[property] is Function)) + destination[property] = this[property]; + } + + public function unapplyTo(object:Object):void + { + for(var prop:String in properties) + unapplyProperty(prop, object); + } + + protected function unapplyProperty(property:String, destination:Object):void + { + if(property in destination && !(destination[property] is Function)) + delete destination[property]; + } + + public function toString():String + { + var styleString:String = "{"; + var i:int; + var total:int = propNames.length; + var prop:String; + for(i = 0; i < total; i++) + { + prop = propNames[i]; + styleString = styleString.concat(prop, ":", properties[prop].toString(), ";"); + } + + styleString = styleString.concat("}"); + + return styleString; + } + + override flash_proxy function getProperty(name:*):* + { + return properties[name]; + } + + override flash_proxy function hasProperty(name:*):Boolean + { + return properties.hasOwnProperty(name); + } + + override flash_proxy function callProperty(name:*, ... parameters):* + { + if(properties.hasOwnProperty(name)) + return function(... args):*{return properties[name]}; + + return null; + } + + override flash_proxy function deleteProperty(name:*):Boolean + { + if(delete properties[name]) + { + propNames.splice(propNames.indexOf(name.toString()), 1); + return true; + } + + return false; + } + + override flash_proxy function setProperty(name:*, value:*):void + { + name = TinytlfUtil.stripSeparators(name); + + if(!properties.hasOwnProperty(name)) + propNames.push(name.toString()); + + properties[name] = value; + } + + override flash_proxy function nextName(index:int):String + { + return propNames[index - 1]; + } + + override flash_proxy function nextNameIndex(index:int):int + { + if(index < propNames.length) + return index + 1; + else + return 0; + } + + override flash_proxy function nextValue(index:int):* + { + return properties[propNames[index - 1]]; + } + } +} + diff --git a/tinytlf-core/src/org/tinytlf/styles/TextStyler.as b/tinytlf-core/src/org/tinytlf/styles/TextStyler.as new file mode 100755 index 0000000..bf86efd --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/styles/TextStyler.as @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.styles +{ + import flash.text.engine.ElementFormat; + import flash.text.engine.FontDescription; + import flash.utils.Dictionary; + + import org.tinytlf.ITextEngine; + + public class TextStyler extends StyleAwareActor implements ITextStyler + { + public function TextStyler(styleObject:Object = null) + { + super(styleObject); + } + + protected var _engine:ITextEngine; + public function get engine():ITextEngine + { + return _engine; + } + + public function set engine(textEngine:ITextEngine):void + { + if(textEngine == _engine) + return; + + _engine = textEngine; + } + + public function getElementFormat(element:*):ElementFormat + { + var styleProp:String; + var format:ElementFormat = new ElementFormat(); + var description:FontDescription = new FontDescription(); + + for(styleProp in this) + { + if(styleProp in format) + format[styleProp] = this[styleProp]; + if(styleProp in description) + description[styleProp] = this[styleProp]; + } + + format.fontDescription = description; + + return format; + } + + protected var styleMap:Dictionary = new Dictionary(true); + + public function describeElement(element:*):Object + { + var obj:IStyleAware = new StyleAwareActor(); + + if(element in styleMap) + obj.mergeWith(styleMap[element]); + + return obj; + } + + public function mapStyle(element:*, value:*):void + { + styleMap[element] = value; + } + + public function unMapStyle(element:*):Boolean + { + if(element in styleMap) + return delete styleMap[element]; + + return false; + } + } +} + diff --git a/tinytlf-core/src/org/tinytlf/util/TinytlfUtil.as b/tinytlf-core/src/org/tinytlf/util/TinytlfUtil.as new file mode 100755 index 0000000..3869729 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/util/TinytlfUtil.as @@ -0,0 +1,334 @@ +package org.tinytlf.util +{ + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.system.Capabilities; + import flash.text.engine.*; + import flash.utils.*; + + import org.tinytlf.ITextEngine; + import org.tinytlf.analytics.IVirtualizer; + import org.tinytlf.layout.properties.LayoutProperties; + import org.tinytlf.util.fte.TextLineUtil; + + public final class TinytlfUtil + { + public static function atomIndexToGlobalIndex(engine:ITextEngine, line:TextLine, atomIndex:int):int + { + var v:IVirtualizer = engine.blockFactory.contentVirtualizer; + var blockStart:int = v.getItemStart(line.textBlock); + return blockStart + line.textBlockBeginIndex + atomIndex; + } + + public static function globalIndexToAtomIndex(engine:ITextEngine, line:TextLine, globalIndex:int):int + { + var v:IVirtualizer = engine.blockFactory.contentVirtualizer; + var blockStart:int = v.getItemStart(line.textBlock); + return globalIndex - blockStart - line.textBlockBeginIndex; + } + + public static function globalIndexToTextLine(engine:ITextEngine, globalIndex:int):TextLine + { + var textBlockVirtualizer:IVirtualizer = engine.layout.textBlockVirtualizer; + var contentVirtualizer:IVirtualizer = engine.blockFactory.contentVirtualizer; + var block:TextBlock = textBlockVirtualizer.getItemFromPosition(globalIndex); + + if(globalIndex >= contentVirtualizer.size) + { + block = textBlockVirtualizer.getItemFromIndex(textBlockVirtualizer.length - 1); + --globalIndex; + } + + return block.getTextLineAtCharIndex(globalIndex - textBlockVirtualizer.getItemStart(block)); + } + + public static function globalIndexToAtomBounds(engine:ITextEngine, globalIndex:int):Rectangle + { + var contentVirtualizer:IVirtualizer = engine.blockFactory.contentVirtualizer; + var b:TextBlock = contentVirtualizer.getItemFromPosition(globalIndex); + + if(globalIndex >= contentVirtualizer.size) + { + b = contentVirtualizer.getItemFromIndex(contentVirtualizer.length - 1); + globalIndex = contentVirtualizer.size - 1; + } + + if(!b) + return null; + + var blockIndex:int = globalIndex - contentVirtualizer.getItemStart(b); + var line:TextLine = b.getTextLineAtCharIndex(blockIndex); + var atomBounds:Rectangle = line.getAtomBounds(blockIndex - line.textBlockBeginIndex); + atomBounds.offset(line.x, line.y); + return atomBounds; + } + + public static function pointToGlobalIndex(engine:ITextEngine, point:Point):int + { + var textBlockVirtualizer:IVirtualizer = engine.layout.textBlockVirtualizer; + var contentVirtualizer:IVirtualizer = engine.blockFactory.contentVirtualizer; + var blocks:Dictionary = textBlockVirtualizer.items; + var b:TextBlock; + var l:TextLine; + + for(var tmp:* in blocks) + { + b = TextBlock(tmp); + l = b.firstLine; + while(l) + { + if(l.hitTestPoint(point.x, point.y)) + break; + + l = l.nextLine; + } + + if(l) + break; + } + + if(!l) + return -1; + + var atomIndex:int = TextLineUtil.getAtomIndexAtPoint(l, point); + + return contentVirtualizer.getItemStart(b) + l.textBlockBeginIndex + atomIndex; + } + + public static function yToTextLine(engine:ITextEngine, y:Number):TextLine + { + var textBlockVirtualizer:IVirtualizer = engine.layout.textBlockVirtualizer; + var b:TextBlock = textBlockVirtualizer.getItemFromPosition(engine.scrollPosition + y); + + if(!b) + { + if(y < 0) + return TextBlock(textBlockVirtualizer.getItemFromIndex(0)).firstLine; + + return null; + } + + var l:TextLine = b.firstLine; + var lp:LayoutProperties = getLP(b); + var h:Number = textBlockVirtualizer.getItemStart(b) + lp.paddingTop; + + while(l) + { + h += l.ascent; + + if(h >= y) + break; + + h += l.descent + lp.leading; + l = l.nextLine; + } + + if(!l && h <= lp.y + lp.height + lp.paddingBottom) + l = b.lastLine; + + return l; + } + + private static var mac:Boolean = (/mac/i).test(Capabilities.os); + + /** + * Useful checker to determine what system you're on. Native Mac + * applications respond to different keys than their Windows or Linux + * counterparts, and every interaction in tinytlf should be the same as + * the native system functions. + */ + public static function isMac():Boolean + { + return mac; + } + + public static function validPoint(p:Point):Boolean + { + return p.x == p.x && p.y == p.y; + } + + /** + * Returns true if all of the flags specified by flagMask are set. + */ + public static function isBitSet(flags:uint, flagMask:uint):Boolean + { + return flagMask == (flags & flagMask); + } + + /** + * Sets the flags specified by flagMask according to value. + * Returns the new bitflag. + * flagMask can be a combination of multiple flags. + */ + public static function updateBits(flags:uint, flagMask:uint, update:Boolean = true):uint + { + if(update) + { + if((flags & flagMask) == flagMask) + return flags; // Nothing to change + // Don't use ^ since flagMask could be a combination of multiple flags + flags |= flagMask; + } + else + { + if((flags & flagMask) == 0) + return flags; // Nothing to change + // Don't use ^ since flagMask could be a combination of multiple flags + flags &= ~flagMask; + } + return flags; + } + + /** + * Retrieves the LayoutProperties object from the argument passed in. + * If no LayoutProperties could be determined, a new instance is + * returned. + * + */ + public static function getLP(from:Object = null):LayoutProperties + { + if(from is LayoutProperties) + return LayoutProperties(from); + + var block:TextBlock; + if(from is TextLine) + block = TextLine(from).textBlock; + else if(from is TextBlock) + block = TextBlock(from); + + if(block) + { + if(block.userData is LayoutProperties) + return LayoutProperties(block.userData); + else + return block.userData = new LayoutProperties(); + } + + return new LayoutProperties(from); + } + + /** + * Like compare, except only primitive types matter.   + * If objectA has a child object with no values and objectB doesn't have + * that object, they still compare as true because no primitive types + * had to be evaluated. + * + * @return True if the two Object's values are the same, False if + * they're different. + */ + public static function compareObjectValues(objectA:Object, + objectB:Object, + exceptions:Object = null):Boolean + { + if(!!objectA != !!objectB) + return false; + + return ( + recursiveCompare(objectA, objectB, exceptions) && + recursiveCompare(objectB, objectA, exceptions) + ); + } + + private static const compareTypes:Dictionary = new Dictionary(); + + private static function recursiveCompare(source:Object, + dest:Object, + exceptions:Object = null):Boolean + { + exceptions ||= {}; + + if(source is Array) + { + if(source.length != dest.length) + return false; + + for(var i:int = 0; i < source.length; i += 1) + { + if(source[i] !== dest[i]) + { + return false; + } + } + + return true; + } + if(source is XML || source is XMLList) + { + if(source.length() != dest.length()) + return false; + + return source === dest; + } + + var accessors:XMLList; + if(!(source.constructor in compareTypes)) + { + var xml:XML = describeType(source); + compareTypes[source.constructor] = xml..accessor.(@access == 'readwrite'); + } + + accessors = compareTypes[source.constructor]; + + var n:String; + for each(var x:XML in accessors) + { + n = x.@name; + + if(n in exceptions) + continue; + + switch(typeof source[n]) + { + case 'object': + case 'xml': + if(!recursiveCompare(source[n], dest[n], exceptions)) + return false; + break; + case 'boolean': + case 'number': + case 'string': + case 'function': + if(source[n] !== dest[n]) + return false; + break; + } + } + + return true; + } + + /** + * Converts a string from underscore or dash separators to no separators. + */ + public static function stripSeparators(str:String):String + { + var s:String = str.replace(/(-|_)(\w)/g, function(...args):String{ + return String(args[2]).toUpperCase(); + }); + + return s.replace(/(-|_)/g, ''); + } + + private static var typeCache:Dictionary = new Dictionary(false); + + public static function describeType(value:Object, refreshCache:Boolean = false):XML + { + if(!(value is Class)) + { + if(value is Proxy) + value = getDefinitionByName(getQualifiedClassName(value)) as Class; + else if(value is Number) + value = Number; + else + value = value.constructor as Class; + } + + if(refreshCache || typeCache[value] == null) + { + typeCache[value] = flash.utils.describeType(value); + } + + return typeCache[value]; + } + } +} \ No newline at end of file diff --git a/tinytlf-core/src/org/tinytlf/util/XMLUtil.as b/tinytlf-core/src/org/tinytlf/util/XMLUtil.as new file mode 100755 index 0000000..c43f0b5 --- /dev/null +++ b/tinytlf-core/src/org/tinytlf/util/XMLUtil.as @@ -0,0 +1,35 @@ +package org.tinytlf.util +{ + public class XMLUtil + { + public static function buildKeyValueAttributes(attributes:XMLList):Object + { + var obj:Object = {}; + var n:int = attributes.length(); + var attr:XML; + + for (var i:int = 0; i < n; ++i) + { + attr = attributes[i]; + obj[TinytlfUtil.stripSeparators(attr.localName())] = attr.toString(); + } + + return obj; + } + + public static function arrayToString(array:Array):String + { + var s:String = ''; + var n:int = array.length; + for(var i:int = 0; i < n; ++i) + { + if(array[i] is XML || array[i] is XMLList) + s += TinytlfUtil.stripSeparators(array[i].toXMLString()); + else + s += TinytlfUtil.stripSeparators(array[i].toString()); + } + + return s; + } + } +} \ No newline at end of file diff --git a/tinytlf-decorations/src/org/tinytlf/decor/BackgroundColorDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/BackgroundColorDecoration.as new file mode 100755 index 0000000..3b3ef13 --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/BackgroundColorDecoration.as @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.decor +{ + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.Rectangle; + import flash.text.engine.TextLineMirrorRegion; + + public class BackgroundColorDecoration extends ContentElementDecoration + { + public function BackgroundColorDecoration(styleObject:Object = null) + { + super(styleObject); + } + + override protected function processTLMR(tlmr:TextLineMirrorRegion):Rectangle + { + var rect:Rectangle = tlmr.bounds.clone(); + rect.y = emBox.y; + rect.height = emBox.height; + rect.offset(tlmr.textLine.x, tlmr.textLine.y); + return rect; + } + + override public function draw(bounds:Vector.):void + { + super.draw(bounds); + + var rect:Rectangle; + var g:Graphics; + var copy:Vector. = bounds.concat(); + var bgColor:uint; + var bgAlpha:Number; + var layer:Sprite; + + while(copy.length > 0) + { + rect = copy.pop(); + + layer = rectToLayer(rect); + if(!layer) + continue; + + g = layer.graphics; + g.lineStyle(); + + bgColor = uint(getStyle('backgroundColor')); + bgAlpha = Number(getStyle('backgroundAlpha')); + + g.beginFill(isNaN(bgColor) ? 0x000000 : bgColor, isNaN(bgAlpha) ? 1 : bgAlpha); + g.drawRect(rect.x, rect.y, rect.width, rect.height); + g.endFill(); + } + } + } +} + diff --git a/tinytlf-decorations/src/org/tinytlf/decor/BorderDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/BorderDecoration.as new file mode 100644 index 0000000..9b32265 --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/BorderDecoration.as @@ -0,0 +1,49 @@ +package org.tinytlf.decor +{ + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.Rectangle; + + import org.tinytlf.decor.BackgroundColorDecoration; + + public class BorderDecoration extends BackgroundColorDecoration + { + public function BorderDecoration(styleObject:Object=null) + { + super(styleObject); + } + + override public function draw(bounds:Vector.):void + { + var rect:Rectangle; + var g:Graphics; + var copy:Vector. = bounds.concat(); + var layer:Sprite; + + while(copy.length > 0) + { + rect = copy.pop(); + + layer = rectToLayer(rect); + if(!layer) + continue; + + g = layer.graphics; + g.lineStyle( + getStyle("thickness") || 1, + getStyle("color") || 0x00, + getStyle("alpha") || 1, + getStyle("pixelHinting") || false, + getStyle("scaleMode") || "normal", + getStyle("caps") || null, + getStyle("joints") || null, + getStyle("miterLimit") || 3); + + g.beginFill(0x00, 0); + g.drawRect(rect.x, rect.y, rect.width, rect.height); + g.lineStyle(); + g.endFill(); + } + } + } +} \ No newline at end of file diff --git a/tinytlf-decorations/src/org/tinytlf/decor/BulletDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/BulletDecoration.as new file mode 100755 index 0000000..4384658 --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/BulletDecoration.as @@ -0,0 +1,41 @@ +package org.tinytlf.decor +{ + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.Rectangle; + + + public class BulletDecoration extends TextDecoration + { + public function BulletDecoration(styleObject:Object = null) + { + super(styleObject); + } + + override public function draw(bounds:Vector.):void + { + var copy:Vector. = bounds.concat(); + var rect:Rectangle; + var g:Graphics; + var diameter:Number = getStyle('diameter') || 4; + var layer:Sprite; + + while(copy.length) + { + rect = copy.pop(); + + layer = rectToLayer(rect); + if(!layer) + continue; + + g = layer.graphics; + + g.beginFill(getStyle('bulletColor') || getStyle('fontColor') || 0x00, + getStyle('bulletAlpha') || getStyle('fontAlpha') || 1); + + g.drawCircle(rect.x + (rect.width - diameter), rect.y + (diameter * 2), diameter * .5); + g.endFill(); + } + } + } +} \ No newline at end of file diff --git a/tinytlf-decorations/src/org/tinytlf/decor/ContentElementDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/ContentElementDecoration.as new file mode 100755 index 0000000..7359ba3 --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/ContentElementDecoration.as @@ -0,0 +1,59 @@ +package org.tinytlf.decor +{ + import flash.geom.Rectangle; + import flash.text.engine.ContentElement; + import flash.text.engine.FontMetrics; + import flash.text.engine.TextLineMirrorRegion; + + import org.tinytlf.util.fte.ContentElementUtil; + + public class ContentElementDecoration extends TextDecoration + { + public function ContentElementDecoration(styleObject:Object = null) + { + super(styleObject); + } + + override public function setup(layer:int = 2, ... parameters):Vector. + { + if(parameters.length < 1) + return super.setup.apply(null, [layer, foreground].concat(parameters)); + + var arg:* = parameters[0]; + if(!(arg is ContentElement)) + return super.setup.apply(null, [layer].concat(parameters)); + + processContentElement(ContentElement(arg)); + + var bounds:Vector. = new Vector.(); + var tlmrs:Vector. = ContentElementUtil.getMirrorRegions(ContentElement(arg)); + var n:int = tlmrs.length; + + var tlmr:TextLineMirrorRegion; + var rect:Rectangle; + + for(var i:int = 0; i < n; ++i) + { + tlmr = tlmrs[i]; + rect = processTLMR(tlmr); + rectToContainer[rect] = ensureLayerExists(engine.layout.getContainerForLine(tlmr.textLine), layer); + bounds.push(rect); + } + + return bounds; + } + + protected var emBox:Rectangle; + + protected function processContentElement(element:ContentElement):void + { + var metrics:FontMetrics = element.elementFormat.getFontMetrics(); + emBox = metrics.emBox; + } + + protected function processTLMR(tlmr:TextLineMirrorRegion):Rectangle + { + return null; + } + } +} \ No newline at end of file diff --git a/tinytlf-decorations/src/org/tinytlf/decor/HorizontalRuleDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/HorizontalRuleDecoration.as new file mode 100755 index 0000000..23c3f1d --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/HorizontalRuleDecoration.as @@ -0,0 +1,40 @@ +package org.tinytlf.decor +{ + import flash.geom.Rectangle; + import flash.text.engine.ContentElement; + import flash.text.engine.TextLine; + + import org.tinytlf.layout.ITextContainer; + import org.tinytlf.util.fte.ContentElementUtil; + + public class HorizontalRuleDecoration extends StrikeThroughDecoration + { + public function HorizontalRuleDecoration(styleObject:Object = null) + { + super(styleObject); + } + + override public function setup(layer:int = 0, ... parameters):Vector. + { + var arg:* = parameters[0]; + if(arg is ContentElement) + { + var lines:Vector. = ContentElementUtil.getTextLines(ContentElement(arg)); + if(!lines.length) + return new []; + + var line:TextLine = lines[0]; + var rect:Rectangle = line.getBounds(line.parent); + var container:ITextContainer; + + rect.width = line.specifiedWidth; + + rectToContainer[rect] = ensureLayerExists(engine.layout.getContainerForLine(line), layer); + + return new [rect]; + } + else + return super.setup.apply(null, [layer].concat(parameters)); + } + } +} \ No newline at end of file diff --git a/tinytlf-decorations/src/org/tinytlf/decor/PopupDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/PopupDecoration.as new file mode 100644 index 0000000..2a48477 --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/PopupDecoration.as @@ -0,0 +1,35 @@ +package org.tinytlf.decor +{ + import flash.display.DisplayObject; + import flash.display.Sprite; + import flash.geom.Rectangle; + + [Style(name="component", type="Object")] + + public class PopupDecoration extends TextDecoration + { + public function PopupDecoration(styleObject:Object = null) + { + super(styleObject); + } + + override public function draw(bounds:Vector.):void + { + var component:DisplayObject = (getStyle('component') as DisplayObject); + if(!component) + return; + + var r:Rectangle = bounds[0]; + var layer:Sprite = rectToLayer(r); + + if(!layer.contains(component)) + layer.addChild(component); + + component.x = r.x; + component.y = r.y; + + //Pass along any styles that may also be public attributes of the component. + applyTo(component); + } + } +} \ No newline at end of file diff --git a/src/org/tinytlf/decor/decorations/StrikeThroughDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/StrikeThroughDecoration.as old mode 100644 new mode 100755 similarity index 60% rename from src/org/tinytlf/decor/decorations/StrikeThroughDecoration.as rename to tinytlf-decorations/src/org/tinytlf/decor/StrikeThroughDecoration.as index d05e753..d3e6985 --- a/src/org/tinytlf/decor/decorations/StrikeThroughDecoration.as +++ b/tinytlf-decorations/src/org/tinytlf/decor/StrikeThroughDecoration.as @@ -4,50 +4,58 @@ * Permission is hereby granted to use, modify, and distribute this file * in accordance with the terms of the license agreement accompanying it. */ -package org.tinytlf.decor.decorations +package org.tinytlf.decor { + import flash.display.Graphics; import flash.display.Sprite; import flash.geom.Point; import flash.geom.Rectangle; - import org.tinytlf.decor.TextDecoration; public class StrikeThroughDecoration extends TextDecoration { - public function StrikeThroughDecoration(styleName:String="") + public function StrikeThroughDecoration(styleObject:Object = null) + { + super(styleObject); + } + + override public function draw(bounds:Vector.):void { - super(styleName); - } - - override public function draw(bounds:Vector., layer:int = 0):void - { - super.draw(bounds, layer); + super.draw(bounds); var start:Point; var end:Point; var rect:Rectangle; - var parent:Sprite; + var g:Graphics; + var layer:Sprite; while(bounds.length > 0) { rect = bounds.pop(); - parent = spriteMap[rect]; + + layer = rectToLayer(rect); + if(!layer) + continue; + + g = layer.graphics; start = new Point(rect.x, rect.y + (rect.height * 0.5)); end = new Point(rect.x + rect.width, rect.y + (rect.height * 0.5)); - parent.graphics.lineStyle( + g.lineStyle( getStyle("weight") || 2, - getStyle("color") || getStyle("color") || 0x00, - getStyle("alpha") || 1, + getStyle("strikethroughColor") || getStyle("color") || 0x00, + getStyle("strikethroughAlpha") || getStyle("alpha") || 1, getStyle("pixelHinting") || false, getStyle("scaleMode") || "normal", getStyle("caps") || null, getStyle("joints") || null, getStyle("miterLimit") || 3); - parent.graphics.moveTo(start.x, start.y); - parent.graphics.lineTo(end.x, end.y); + g.moveTo(start.x, start.y); + g.lineTo(end.x, end.y); + + g.lineStyle(); } } } diff --git a/tinytlf-decorations/src/org/tinytlf/decor/UnderlineDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/UnderlineDecoration.as new file mode 100755 index 0000000..3bcd737 --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/UnderlineDecoration.as @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.decor +{ + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.text.engine.ContentElement; + import flash.text.engine.FontMetrics; + import flash.text.engine.TextLineMirrorRegion; + + public class UnderlineDecoration extends ContentElementDecoration + { + public function UnderlineDecoration(styleObject:Object = null) + { + super(styleObject); + } + + override protected function processContentElement(element:ContentElement):void + { + super.processContentElement(element); + var metrics:FontMetrics = element.elementFormat.getFontMetrics(); + setStyle("underlineThickness", metrics.underlineThickness); + } + + override protected function processTLMR(tlmr:TextLineMirrorRegion):Rectangle + { + var rect:Rectangle = tlmr.bounds.clone(); + rect.y = emBox.y; + rect.height = emBox.height - getStyle('underlineThickness'); + rect.offset(tlmr.textLine.x, tlmr.textLine.y); + return rect; + } + + override public function draw(bounds:Vector.):void + { + super.draw(bounds); + + var start:Point; + var end:Point; + var rect:Rectangle; + var g:Graphics; + var copy:Vector. = bounds.concat(); + var thickness:Number = getStyle("underlineThickness") || 2; + var layer:Sprite; + + while(copy.length > 0) + { + rect = copy.pop(); + + layer = rectToLayer(rect); + if(!layer) + continue; + + g = layer.graphics; + start = new Point(rect.left, rect.bottom - thickness); + end = new Point(rect.right, rect.bottom - thickness); + + g.lineStyle( + thickness, + getStyle("underlineColor") || getStyle("color") || 0x00, + getStyle("underlineAlpha") || getStyle("alpha") || 1, + getStyle("pixelHinting") || false, + getStyle("scaleMode") || "normal", + getStyle("caps") || null, + getStyle("joints") || null, + getStyle("miterLimit") || 3); + + g.moveTo(start.x, start.y); + g.lineTo(end.x, end.y); + g.endFill(); + g.lineStyle(); + } + } + } +} + diff --git a/tinytlf-decorations/src/org/tinytlf/decor/selection/CaretDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/selection/CaretDecoration.as new file mode 100755 index 0000000..b124ad6 --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/selection/CaretDecoration.as @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.decor.selection +{ + import flash.display.Graphics; + import flash.display.Shape; + import flash.display.Sprite; + import flash.events.TimerEvent; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.text.engine.TextLine; + import flash.utils.Timer; + + import org.tinytlf.decor.TextDecoration; + + + public class CaretDecoration extends SelectionDecorationBase + { + public function CaretDecoration(styleObject:Object = null) + { + super(styleObject); + } + + private var timer:Timer; + private var g:Graphics; + private var rect:Rectangle; + + override public function setup(layer:int = 0, ... args):Vector. + { + if(!timer) + timer = new Timer(365); + else + timer.stop(); + + var wrapper:Object = args[0]; + var index:int = wrapper.caretIndex; + + return super.setup.apply(null, [layer].concat(new Point(index, index + 1))); + } + + override public function draw(bounds:Vector.):void + { + super.draw(bounds); + + if(!bounds.length) + return; + + rect = bounds[0]; + + var layer:Sprite = rectToLayer(rect); + if(!layer) + return; + + g = Shape(layer.addChild(new Shape())).graphics; + + if(!timer.hasEventListener(TimerEvent.TIMER)) + timer.addEventListener(TimerEvent.TIMER, toggle); + + if(!timer.running) + { + toggle(null); + timer.start(); + } + } + + override public function destroy():void + { + super.destroy(); + + if(timer) + { + timer.stop(); + timer.removeEventListener(TimerEvent.TIMER, toggle); + } + + timer = null; + showing = false; + + if(g) + g.clear(); + g = null; + rect = null; + } + + private var showing:Boolean = false; + + private function toggle(event:TimerEvent):void + { + if(!g) + return; + + g.clear(); + + if(showing = !showing) + return; + + var right:int = int(Boolean(getStyle('position') == 'right')); + + g.lineStyle(getStyle('caretWeight') || 1, getStyle('caretColor')); + g.moveTo(rect.x + right * rect.width, rect.y); + g.lineTo(rect.x + right * rect.width, rect.y + rect.height); + } + + override protected function getLineRect(line:TextLine, selectionIndicies:Point):Rectangle + { + line.stage.focus = line; + return super.getLineRect(line, selectionIndicies); + } + } +} \ No newline at end of file diff --git a/tinytlf-decorations/src/org/tinytlf/decor/selection/SelectionDecorationBase.as b/tinytlf-decorations/src/org/tinytlf/decor/selection/SelectionDecorationBase.as new file mode 100644 index 0000000..78045b0 --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/selection/SelectionDecorationBase.as @@ -0,0 +1,188 @@ +package org.tinytlf.decor.selection +{ + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.text.engine.TextBlock; + import flash.text.engine.TextLine; + + import org.tinytlf.analytics.IVirtualizer; + import org.tinytlf.decor.TextDecor; + import org.tinytlf.decor.TextDecoration; + import org.tinytlf.layout.ITextContainer; + + public class SelectionDecorationBase extends TextDecoration + { + public function SelectionDecorationBase(styleObject:Object = null) + { + super(styleObject); + } + + override public function setup(layer:int = 2, ... parameters):Vector. + { + // We need to resolve some rects from our selection indicies. + var rects:Vector. = new []; + // this must be a Point object + var pt:Point = Point(parameters[0]).clone(); + var contentVirtualizer:IVirtualizer = engine.blockFactory.contentVirtualizer; + var block:TextBlock; + var index:int = 0; + + var indicies:Point; + var start:Number = pt.x; + + while(start < pt.y) + { + index = contentVirtualizer.getIndexFromPosition(start); + + if(start == contentVirtualizer.size) + index = contentVirtualizer.length - 1; + + if(index == -1) + break; + + block = contentVirtualizer.getItemFromIndex(index); + + if(block) + { + indicies = getBlockSelectionIndicies(block, pt.clone()); + + if(indicies.x == indicies.x && indicies.y == indicies.y) + rects = rects.concat(getBlockRects(block, indicies)); + else + break; + } + + if(start == contentVirtualizer.size) + break; + + start = contentVirtualizer.getItemStart(block) + contentVirtualizer.getItemSize(block); + } + + return rects; + } + + protected function getBlockSelectionIndicies(block:TextBlock, selection:Point):Point + { + var contentVirtualizer:IVirtualizer = engine.blockFactory.contentVirtualizer; + var blockIndex:int = contentVirtualizer.getItemIndex(block); + var contentItem:* = contentVirtualizer.getItemFromIndex(blockIndex); + var start:Number = contentVirtualizer.getItemStart(contentItem); + var size:Number = contentVirtualizer.getItemSize(contentItem); + var end:Number = start + size; + + var p:Point = new Point(); + + if(selection.y < start || selection.x > end) + p.x = p.y = NaN; + else + { + if(selection.x <= start) + p.x = 0; + else if(selection.x <= end) + p.x = selection.x - start; + + if(selection.y > end) + p.y = end; + else if(selection.y <= end) + p.y = selection.y - start; + + if(selection.x > end) + p.x = p.y = NaN; + } + + return p; + } + + protected function getBlockRects(block:TextBlock, selectionIndicies:Point):Vector. + { + var contentVirtualizer:IVirtualizer = engine.blockFactory.contentVirtualizer; + var blockSize:Number = contentVirtualizer.getItemSize(block); + + if(selectionIndicies.x == 0 && selectionIndicies.y >= blockSize - 1) + { + return super.setup(TextDecor.SELECTION_LAYER, block); + } + + var rects:Vector. = new []; + var blockLength:int = contentVirtualizer.getItemSize(block); + var line:TextLine = block.getTextLineAtCharIndex(Math.min(selectionIndicies.x, blockLength - 1)); + var indicies:Point; + + while(line) + { + indicies = getLineSelectionIndicies(line, selectionIndicies); + if( indicies.x == indicies.x && + indicies.y == indicies.y && + indicies.y > indicies.x) + { + rects.push(getLineRect(line, indicies)); + line = line.nextLine; + } + else + { + break; + } + } + + return rects; + } + + protected function getLineSelectionIndicies(line:TextLine, selection:Point):Point + { + var p:Point = new Point(); + var begin:int = line.textBlockBeginIndex; + var end:int = begin + line.atomCount; + + if(selection.y < begin || selection.x > end) + { + p.x = p.y = NaN; + } + else + { + if(selection.x <= begin) + p.x = 0; + else if(selection.x > begin) + p.x = selection.x - begin; + + if(selection.y > end) + p.y = end; + else if(selection.y <= end) + p.y = selection.y - begin; + } + + return p; + } + + protected function getLineRect(line:TextLine, selectionIndicies:Point):Rectangle + { + var startIndex:int = selectionIndicies.x; + var endIndex:int = selectionIndicies.y; + + if(startIndex >= line.atomCount) + startIndex = line.atomCount - 1; + if(endIndex >= line.atomCount) + endIndex = line.atomCount - 1; + + var rect:Rectangle = line.getAtomBounds(startIndex); + rect.width ||= 1; + rect.height ||= 1; + + var rect2:Rectangle = line.getAtomBounds(Math.max(endIndex, 0)); + + if(endIndex > line.atomCount) + rect2 = new Rectangle(line.specifiedWidth); + + rect2.width ||= 1; + rect2.height ||= 1; + + rect = rect.union(rect2); + + rect.offset(line.x, line.y); + + var container:ITextContainer = engine.layout.getContainerForLine(line); + rectToContainer[rect] = ensureLayerExists(container, TextDecor.SELECTION_LAYER); + + return rect; + } + } +} \ No newline at end of file diff --git a/tinytlf-decorations/src/org/tinytlf/decor/selection/StandardSelectionDecoration.as b/tinytlf-decorations/src/org/tinytlf/decor/selection/StandardSelectionDecoration.as new file mode 100755 index 0000000..bc521e6 --- /dev/null +++ b/tinytlf-decorations/src/org/tinytlf/decor/selection/StandardSelectionDecoration.as @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.decor.selection +{ + import flash.display.*; + import flash.geom.Rectangle; + + public class StandardSelectionDecoration extends SelectionDecorationBase + { + public function StandardSelectionDecoration(styleObject:Object = null) + { + super(styleObject); + } + + override public function draw(bounds:Vector.):void + { + super.draw(bounds); + + var rect:Rectangle; + var copy:Vector. = bounds.concat(); + var g:Graphics; + var color:uint; + var alpha:Number; + var layer:Sprite; + + while (copy.length > 0) + { + rect = copy.pop(); + + layer = rectToLayer(rect); + if(!layer) + continue; + + g = layer.graphics; + + color = uint(getStyle("selectionColor")) || 0x0000CC; + alpha = Number(getStyle("selectionAlpha")) || 1; + + g.beginFill(color, alpha); + g.drawRect(rect.x, rect.y, rect.width, rect.height); + g.endFill(); + } + } + } +} + diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/BackspaceBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/BackspaceBehavior.as new file mode 100644 index 0000000..4e6cc77 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/BackspaceBehavior.as @@ -0,0 +1,30 @@ +package org.tinytlf.behaviors +{ + import flash.geom.Point; + + import org.tinytlf.operations.*; + + public class BackspaceBehavior extends OperationFactoryBehavior + { + [Event("keyDown")] + public function backspace():void + { + var op:CompositeOperation = new CompositeOperation(); + + if(!validSelection) + selection = new Point(--caret, caret + 1); + else + caret = selection.x; + + op.add( + new TextRemoveOperation({start:selection.x, end:selection.y}), + new CaretMoveOperation({caret: caret}), + new TextSelectionOperation({selection: null}) + ); + + op.runAtEnd(new InvalidateEngineOperation()); + + initAndExecute(push(op)); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/BackspaceCtrlBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/BackspaceCtrlBehavior.as new file mode 100644 index 0000000..0cf08b5 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/BackspaceCtrlBehavior.as @@ -0,0 +1,31 @@ +package org.tinytlf.behaviors +{ + import org.tinytlf.operations.*; + import org.tinytlf.util.TinytlfUtil; + import org.tinytlf.util.fte.TextLineUtil; + + public class BackspaceCtrlBehavior extends OperationFactoryBehavior + { + [Event("keyDown")] + public function backspace():void + { + var op:CompositeOperation = new CompositeOperation(); + + //Translate to the local atom index. + var atomIndex:int = TinytlfUtil.globalIndexToAtomIndex(engine, line, caret); + + //Get the previous word boundary + var start:int = TextLineUtil.getAtomWordBoundary(line, atomIndex); + + op.add( + new TextRemoveOperation({start:start, end: caret}), + new CaretMoveOperation({caret: TinytlfUtil.atomIndexToGlobalIndex(engine, line, start)}), + new TextSelectionOperation({selection: null}) + ); + + op.runAtEnd(new InvalidateEngineOperation()); + + initAndExecute(push(op)); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/BoldBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/BoldBehavior.as new file mode 100644 index 0000000..2dfb9dd --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/BoldBehavior.as @@ -0,0 +1,15 @@ +package org.tinytlf.behaviors +{ + public class BoldBehavior extends OperationFactoryBehavior + { + public function BoldBehavior() + { + super(); + } + + [Event("keyDown")] + public function down():void + { + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/CopyBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/CopyBehavior.as new file mode 100644 index 0000000..2e0ac94 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/CopyBehavior.as @@ -0,0 +1,10 @@ +package org.tinytlf.behaviors +{ + public class CopyBehavior extends OperationFactoryBehavior + { + [Event("copy")] + public function copy():void + { + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/DeleteBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/DeleteBehavior.as new file mode 100644 index 0000000..78b453a --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/DeleteBehavior.as @@ -0,0 +1,28 @@ +package org.tinytlf.behaviors +{ + import flash.geom.Point; + + import org.tinytlf.operations.*; + + public class DeleteBehavior extends OperationFactoryBehavior + { + [Event("keyDown")] + public function deleteChars():void + { + var op:CompositeOperation = new CompositeOperation(); + + if(!validSelection) + selection = new Point(caret, caret + 1); + + op.add( + new TextRemoveOperation({start:selection.x, end:selection.y}), + new CaretMoveOperation({caret: selection.x + 1}), + new TextSelectionOperation({selection: null}) + ); + + op.runAtEnd(new InvalidateEngineOperation()); + + initAndExecute(push(op)); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/DownArrowBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/DownArrowBehavior.as new file mode 100644 index 0000000..b3745c8 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/DownArrowBehavior.as @@ -0,0 +1,43 @@ +package org.tinytlf.behaviors +{ + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.text.engine.TextLine; + + import org.tinytlf.util.TinytlfUtil; + import org.tinytlf.util.fte.TextLineUtil; + + public class DownArrowBehavior extends KeySelectionBehaviorBase + { + public function DownArrowBehavior() + { + super(); + } + + + override protected function getAnchor():Point + { + var newCaret:int = caret; + var atomIndex:int = TinytlfUtil.globalIndexToAtomIndex(engine, line, caret); + var bounds:Rectangle = TinytlfUtil.globalIndexToAtomBounds(engine, caret); + + var l:TextLine = line; + + if(l.nextLine) + { + l = l.nextLine; + var pt:Point = l.localToGlobal(new Point(bounds.x, 1)); + newCaret = TinytlfUtil.atomIndexToGlobalIndex( + engine, l, + TextLineUtil.getAtomIndexAtPoint(l, pt) + ); + } + else + { + newCaret = TinytlfUtil.atomIndexToGlobalIndex(engine, l, l.atomCount); + } + + return new Point(newCaret, 0); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/KeySelectionBehaviorBase.as b/tinytlf-edit/src/org/tinytlf/behaviors/KeySelectionBehaviorBase.as new file mode 100644 index 0000000..7d223a3 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/KeySelectionBehaviorBase.as @@ -0,0 +1,73 @@ +package org.tinytlf.behaviors +{ + import flash.events.KeyboardEvent; + import flash.geom.Point; + import flash.text.engine.TextBlock; + import flash.text.engine.TextLine; + + import org.tinytlf.analytics.IVirtualizer; + + public class KeySelectionBehaviorBase extends SelectionBehaviorBase + { + public function KeySelectionBehaviorBase() + { + super(); + } + + [Event("keyDown")] + override public function downAction():void + { + var pt:Point = getSelection(); + + var k:KeyboardEvent = KeyboardEvent(finalEvent); + if(k.shiftKey) + engine.select(pt.x, pt.y); + else + engine.select(); + + var anchor:Point = getAnchor(); + engine.caretIndex = anchor.x; + + //assign focus to the proper line + assignFocus(); + } + + override protected function getSelection():Point + { + var pt:Point = selection; + var nextCaret:Point = getAnchor(); + + if(!validSelection) + pt = new Point(caret, caret); + + if(caret <= pt.x) + pt.x = nextCaret.x; + else if(caret > pt.x) + pt.y = nextCaret.x; + + return pt; + } + + protected function assignFocus():void + { + var textBlockVirtualizer:IVirtualizer = engine.layout.textBlockVirtualizer; + var contentVirtualizer:IVirtualizer = engine.blockFactory.contentVirtualizer; + + var caret:int = engine.caretIndex; + + if(caret < 0) + caret = 0; + if(caret >= contentVirtualizer.size) + caret = contentVirtualizer.size - 1; + + var block:TextBlock = contentVirtualizer.getItemFromPosition(caret); + var blockStart:int = textBlockVirtualizer.getItemStart(block); + var newLine:TextLine = block.getTextLineAtCharIndex(caret - blockStart); + + if(newLine != line) + { + line.stage.focus = container.target; + } + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/LeftArrowBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/LeftArrowBehavior.as new file mode 100644 index 0000000..fb2a377 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/LeftArrowBehavior.as @@ -0,0 +1,29 @@ +package org.tinytlf.behaviors +{ + import flash.events.KeyboardEvent; + import flash.geom.Point; + + import org.tinytlf.util.TinytlfUtil; + + public class LeftArrowBehavior extends KeySelectionBehaviorBase + { + public function LeftArrowBehavior() + { + super(); + } + + override protected function getAnchor():Point + { + var k:KeyboardEvent = finalEvent as KeyboardEvent; + if(validSelection && !k.shiftKey) + { + if(caret > selection.x) + return new Point(selection.x, 0); + + return new Point(caret, 0); + } + + return new Point(caret - 1, 0); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/LeftArrowCtrlBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/LeftArrowCtrlBehavior.as new file mode 100644 index 0000000..7e50d5f --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/LeftArrowCtrlBehavior.as @@ -0,0 +1,23 @@ +package org.tinytlf.behaviors +{ + import flash.events.MouseEvent; + import flash.geom.Point; + + import org.tinytlf.util.TinytlfUtil; + import org.tinytlf.util.fte.TextLineUtil; + + public class LeftArrowCtrlBehavior extends LeftArrowBehavior + { + public function LeftArrowCtrlBehavior() + { + super(); + } + + override protected function getAnchor():Point + { + var caretAtom:int = TinytlfUtil.globalIndexToAtomIndex(engine, line, caret); + var newCaret:int = TextLineUtil.getAtomWordBoundary(line, caretAtom); + return new Point(TinytlfUtil.atomIndexToGlobalIndex(engine, line, newCaret), 0); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/OperationFactoryBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/OperationFactoryBehavior.as new file mode 100644 index 0000000..f7ff181 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/OperationFactoryBehavior.as @@ -0,0 +1,40 @@ +package org.tinytlf.behaviors +{ + import flash.events.Event; + + import org.tinytlf.conversion.IHTMLNode; + import org.tinytlf.interaction.IEditInteractor; + import org.tinytlf.operations.ITextOperation; + + public class OperationFactoryBehavior extends MultiGestureBehavior + { + protected var model:IHTMLNode; + protected var interactor:IEditInteractor; + + override protected function act(events:Vector.):void + { + model = engine.blockFactory.data as IHTMLNode; + interactor = engine.interactor as IEditInteractor; + + if(!model || !interactor) + return; + + super.act(events); + } + + protected function push(op:ITextOperation):ITextOperation + { + return interactor.push(op); + } + + protected function initAndExecute(...ops):void + { + ops.forEach(ieCallback); + } + + private function ieCallback(op:ITextOperation, ...args):void + { + op.initialize(model).execute(); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/RedoBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/RedoBehavior.as new file mode 100644 index 0000000..71690b1 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/RedoBehavior.as @@ -0,0 +1,11 @@ +package org.tinytlf.behaviors +{ + public class RedoBehavior extends OperationFactoryBehavior + { + [Event("keyDown")] + public function redo():void + { + interactor.redo().execute(); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/RightArrowBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/RightArrowBehavior.as new file mode 100644 index 0000000..0ad1759 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/RightArrowBehavior.as @@ -0,0 +1,29 @@ +package org.tinytlf.behaviors +{ + import flash.events.KeyboardEvent; + import flash.geom.Point; + + import org.tinytlf.util.TinytlfUtil; + + public class RightArrowBehavior extends KeySelectionBehaviorBase + { + public function RightArrowBehavior() + { + super(); + } + + override protected function getAnchor():Point + { + var k:KeyboardEvent = finalEvent as KeyboardEvent; + if(validSelection && !k.shiftKey) + { + if(caret < selection.y) + return new Point(selection.y, 0); + + return new Point(caret, 0); + } + + return new Point(caret + 1, 0); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/RightArrowCtrlBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/RightArrowCtrlBehavior.as new file mode 100644 index 0000000..46d906a --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/RightArrowCtrlBehavior.as @@ -0,0 +1,26 @@ +package org.tinytlf.behaviors +{ + import flash.geom.Point; + + import org.tinytlf.util.TinytlfUtil; + import org.tinytlf.util.fte.TextLineUtil; + + public class RightArrowCtrlBehavior extends RightArrowBehavior + { + public function RightArrowCtrlBehavior() + { + super(); + } + + override protected function getAnchor():Point + { + var caretAtom:int = TinytlfUtil.globalIndexToAtomIndex(engine, line, caret - 1); + var newCaret:int = TextLineUtil.getAtomWordBoundary(line, caretAtom, false); + + if(newCaret == caretAtom) + ++newCaret; + + return new Point(TinytlfUtil.atomIndexToGlobalIndex(engine, line, newCaret), 0); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/ShiftEnterBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/ShiftEnterBehavior.as new file mode 100644 index 0000000..9dbfb3b --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/ShiftEnterBehavior.as @@ -0,0 +1,29 @@ +package org.tinytlf.behaviors +{ + import org.tinytlf.operations.*; + + public class ShiftEnterBehavior extends OperationFactoryBehavior + { + [Event("keyDown")] + public function down():void + { + var op:CompositeOperation = new CompositeOperation(); + + if(validSelection) + { + op.add( + new TextRemoveOperation({start: selection.x, end: selection.y}), + new CaretMoveOperation({caret: selection.x + 1}), + new TextSelectionOperation({selection: null}) + ); + } + + var index:int = 0;//model.getChildIndexAtPosition(caret); + + op.add(new SplitParagraphOperation({caret: caret, nodeIndex: index})); + op.runAtEnd(new InvalidateEngineOperation()); + + initAndExecute(push(op)); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/TextInsertBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/TextInsertBehavior.as new file mode 100644 index 0000000..9b78092 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/TextInsertBehavior.as @@ -0,0 +1,40 @@ +package org.tinytlf.behaviors +{ + import flash.events.Event; + import flash.events.KeyboardEvent; + + import org.tinytlf.operations.*; + + public class TextInsertBehavior extends OperationFactoryBehavior + { + [Event("keyDown")] + public function insert(events:Vector.):void + { + var maxCaret:Number = NaN; + var evt:KeyboardEvent = finalEvent as KeyboardEvent; + var chars:String = String.fromCharCode(evt.charCode); + + var op:CompositeOperation = new CompositeOperation(); + + if(validSelection) + { + op.add(new TextRemoveOperation({start:selection.x, end:selection.y})); + + if(caret == selection.y) + caret -= (selection.y - selection.x); + +// maxCaret = model.length - (selection.y - selection.x); + } + + op.add( + new TextInsertOperation({start:caret, value:chars, end:caret + 1}), + new CaretMoveOperation({caret: ++caret, maxCaret: maxCaret}), + new TextSelectionOperation({selection: null}) + ); + + op.runAtEnd(new InvalidateEngineOperation()); + + initAndExecute(push(op)); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/UndoBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/UndoBehavior.as new file mode 100644 index 0000000..b390c3e --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/UndoBehavior.as @@ -0,0 +1,11 @@ +package org.tinytlf.behaviors +{ + public class UndoBehavior extends OperationFactoryBehavior + { + [Event("keyDown")] + public function undo():void + { + interactor.undo().backout(); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/behaviors/UpArrowBehavior.as b/tinytlf-edit/src/org/tinytlf/behaviors/UpArrowBehavior.as new file mode 100644 index 0000000..e52da30 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/behaviors/UpArrowBehavior.as @@ -0,0 +1,46 @@ +package org.tinytlf.behaviors +{ + import flash.events.KeyboardEvent; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.text.engine.TextLine; + + import org.tinytlf.util.TinytlfUtil; + import org.tinytlf.util.fte.TextLineUtil; + + public class UpArrowBehavior extends KeySelectionBehaviorBase + { + public function UpArrowBehavior() + { + super(); + } + + override protected function getAnchor():Point + { + var newCaret:int = caret; + var atomIndex:int = TinytlfUtil.globalIndexToAtomIndex(engine, line, caret); + var bounds:Rectangle = TinytlfUtil.globalIndexToAtomBounds(engine, caret); + + if(!bounds) + return null; + + var l:TextLine = line; + + if(l.previousLine) + { + l = l.previousLine; + var pt:Point = l.localToGlobal(new Point(bounds.x, 1)); + newCaret = TinytlfUtil.atomIndexToGlobalIndex( + engine, l, + TextLineUtil.getAtomIndexAtPoint(l, pt) + ); + } + else + { + newCaret = TinytlfUtil.atomIndexToGlobalIndex(engine, l, -1); + } + + return new Point(newCaret, 0); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/conversion/XMLEditableBlockFactory.as b/tinytlf-edit/src/org/tinytlf/conversion/XMLEditableBlockFactory.as new file mode 100644 index 0000000..8ddddbc --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/conversion/XMLEditableBlockFactory.as @@ -0,0 +1,16 @@ +package org.tinytlf.conversion +{ + public class XMLEditableBlockFactory extends HTMLBlockFactory + { + public function XMLEditableBlockFactory() + { + super(); + } + + override public function preRender():void + { + // TODO: This is where I should re-validate any nodes that have been + // modified by editing. + } + } +} diff --git a/tinytlf-edit/src/org/tinytlf/gestures/BackspaceCtrlGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/BackspaceCtrlGesture.as new file mode 100755 index 0000000..75705bd --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/BackspaceCtrlGesture.as @@ -0,0 +1,17 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + + import org.tinytlf.util.TinytlfUtil; + + [Event("keyDown")] + + public class BackspaceCtrlGesture extends BackspaceGesture + { + override public function backspace(event:KeyboardEvent):Boolean + { + var ctrlKey:Boolean = TinytlfUtil.isMac() ? event.altKey : event.ctrlKey; + return super.backspace(event) && ctrlKey; + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/BackspaceGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/BackspaceGesture.as new file mode 100755 index 0000000..74621ca --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/BackspaceGesture.as @@ -0,0 +1,23 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + import flash.ui.Keyboard; + + + [Event("keyDown")] + + public class BackspaceGesture extends Gesture + { + public function BackspaceGesture() + { + super(); + + hsm.appendChild(); + } + + public function backspace(event:KeyboardEvent):Boolean + { + return event.keyCode == Keyboard.BACKSPACE; + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/CommandBGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/CommandBGesture.as new file mode 100644 index 0000000..436c8ee --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/CommandBGesture.as @@ -0,0 +1,22 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + + [Event("keyDown")] + public class CommandBGesture extends Gesture + { + public function CommandBGesture() + { + super(); + + hsm.appendChild(); + } + + public function b(event:KeyboardEvent):Boolean + { + var char:String = String.fromCharCode(event.charCode); + var result:Boolean = (char == 'b' || char == 'B') && event.charCode && event.ctrlKey; + return result; + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/DeleteCtrlGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/DeleteCtrlGesture.as new file mode 100755 index 0000000..6ddc4ed --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/DeleteCtrlGesture.as @@ -0,0 +1,13 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + + [Event("keyDown")] + public class DeleteCtrlGesture extends DeleteGesture + { + override public function deleteKey(event:KeyboardEvent):Boolean + { + return super.deleteKey(event) && event.ctrlKey; + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/DeleteGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/DeleteGesture.as new file mode 100755 index 0000000..d39313a --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/DeleteGesture.as @@ -0,0 +1,23 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + import flash.ui.Keyboard; + + + [Event("keyDown")] + + public class DeleteGesture extends Gesture + { + public function DeleteGesture() + { + super(); + + hsm.appendChild(); + } + + public function deleteKey(event:KeyboardEvent):Boolean + { + return event.keyCode == Keyboard.DELETE; + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/DownArrowCtrlGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/DownArrowCtrlGesture.as new file mode 100755 index 0000000..398bde1 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/DownArrowCtrlGesture.as @@ -0,0 +1,16 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + + import org.tinytlf.util.TinytlfUtil; + + [Event("keyDown")] + + public class DownArrowCtrlGesture extends DownArrowGesture + { + override public function down(event:KeyboardEvent):Boolean + { + return super.down(event) && (TinytlfUtil.isMac() ? event.altKey : event.ctrlKey); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/DownArrowGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/DownArrowGesture.as new file mode 100755 index 0000000..c0dbefa --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/DownArrowGesture.as @@ -0,0 +1,22 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + import flash.ui.Keyboard; + + [Event("keyDown")] + + public class DownArrowGesture extends Gesture + { + public function DownArrowGesture() + { + super(); + + hsm.appendChild(); + } + + public function down(event:KeyboardEvent):Boolean + { + return (event.keyCode === Keyboard.DOWN); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/LeftArrowCtrlGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/LeftArrowCtrlGesture.as new file mode 100755 index 0000000..6d0c0dd --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/LeftArrowCtrlGesture.as @@ -0,0 +1,16 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + + import org.tinytlf.util.TinytlfUtil; + + [Event("keyDown")] + + public class LeftArrowCtrlGesture extends LeftArrowGesture + { + override public function left(event:KeyboardEvent):Boolean + { + return super.left(event) && (TinytlfUtil.isMac() ? event.altKey : event.ctrlKey); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/LeftArrowGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/LeftArrowGesture.as new file mode 100755 index 0000000..6f90220 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/LeftArrowGesture.as @@ -0,0 +1,27 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + import flash.ui.Keyboard; + + [Event("keyDown")] + public class LeftArrowGesture extends Gesture + { + public function LeftArrowGesture() + { + super(); + + hsm.appendChild(); + hsm.appendChild(); + } + + public function shift(event:KeyboardEvent):Boolean + { + return (event.keyCode == Keyboard.SHIFT && event.shiftKey); + } + + public function left(event:KeyboardEvent):Boolean + { + return (event.keyCode === Keyboard.LEFT); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/RedoGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/RedoGesture.as new file mode 100644 index 0000000..1d732d2 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/RedoGesture.as @@ -0,0 +1,27 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + + import org.tinytlf.util.TinytlfUtil; + + [Event("keyDown")] + public class RedoGesture extends Gesture + { + public function RedoGesture() + { + super(); + + hsm.appendChild(); + } + + public function redo(event:KeyboardEvent):Boolean + { + var char:String = String.fromCharCode(event.charCode); + + if(TinytlfUtil.isMac()) + return char === 'z' && event.shiftKey && event.ctrlKey; + + return char === 'y' && event.ctrlKey; + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/RightArrowCtrlGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/RightArrowCtrlGesture.as new file mode 100755 index 0000000..b8e3954 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/RightArrowCtrlGesture.as @@ -0,0 +1,16 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + + import org.tinytlf.util.TinytlfUtil; + + [Event("keyDown")] + + public class RightArrowCtrlGesture extends RightArrowGesture + { + override public function right(event:KeyboardEvent):Boolean + { + return super.right(event) && (TinytlfUtil.isMac() ? event.altKey : event.ctrlKey); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/RightArrowGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/RightArrowGesture.as new file mode 100755 index 0000000..36b23a2 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/RightArrowGesture.as @@ -0,0 +1,29 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + import flash.ui.Keyboard; + + + [Event("keyDown")] + + public class RightArrowGesture extends Gesture + { + public function RightArrowGesture() + { + super(); + + hsm.appendChild(); + hsm.appendChild(); + } + + public function shift(event:KeyboardEvent):Boolean + { + return (event.keyCode == Keyboard.SHIFT && event.shiftKey); + } + + public function right(event:KeyboardEvent):Boolean + { + return (event.keyCode === Keyboard.RIGHT); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/ShiftEnterGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/ShiftEnterGesture.as new file mode 100644 index 0000000..be8e49e --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/ShiftEnterGesture.as @@ -0,0 +1,21 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + import flash.ui.Keyboard; + + [Event("keyDown")] + public class ShiftEnterGesture extends Gesture + { + public function ShiftEnterGesture() + { + super(); + + hsm.appendChild(); + } + + public function enter(event:KeyboardEvent):Boolean + { + return (event.keyCode == Keyboard.ENTER && event.shiftKey); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/TextEntryGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/TextEntryGesture.as new file mode 100644 index 0000000..64b9026 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/TextEntryGesture.as @@ -0,0 +1,28 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + import flash.ui.Keyboard; + + [Event("keyDown")] + public class TextEntryGesture extends Gesture + { + public function TextEntryGesture() + { + super(); + + hsm.appendChild(); + } + + public function down(event:KeyboardEvent):Boolean + { + var char:String = String.fromCharCode(event.charCode); + var result:Boolean = char != '' && event.charCode; + result &&= !(event.keyCode == Keyboard.ENTER && event.shiftKey); + result &&= !event.ctrlKey; + result &&= !(event.keyCode == Keyboard.DELETE); + result &&= !(event.keyCode == Keyboard.BACKSPACE); + + return result; + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/UndoGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/UndoGesture.as new file mode 100644 index 0000000..313fc34 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/UndoGesture.as @@ -0,0 +1,27 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + + import org.tinytlf.util.TinytlfUtil; + + [Event("keyDown")] + public class UndoGesture extends Gesture + { + public function UndoGesture() + { + super(); + + hsm.appendChild(); + } + + public function undo(event:KeyboardEvent):Boolean + { + var result:Boolean = event.ctrlKey && (event.charCode == 122); //small 'z' + + if(TinytlfUtil.isMac()) + result &&= !event.shiftKey; + + return result; + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/UpArrowCtrlGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/UpArrowCtrlGesture.as new file mode 100755 index 0000000..99cb94b --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/UpArrowCtrlGesture.as @@ -0,0 +1,16 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + + import org.tinytlf.util.TinytlfUtil; + + [Event("keyDown")] + + public class UpArrowCtrlGesture extends UpArrowGesture + { + override public function up(event:KeyboardEvent):Boolean + { + return super.up(event) && (TinytlfUtil.isMac() ? event.altKey : event.ctrlKey); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/gestures/UpArrowGesture.as b/tinytlf-edit/src/org/tinytlf/gestures/UpArrowGesture.as new file mode 100755 index 0000000..3d0be2d --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/gestures/UpArrowGesture.as @@ -0,0 +1,23 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + import flash.ui.Keyboard; + + + [Event("keyDown")] + + public class UpArrowGesture extends Gesture + { + public function UpArrowGesture() + { + super(); + + hsm.appendChild(); + } + + public function up(event:KeyboardEvent):Boolean + { + return (event.keyCode === Keyboard.UP); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/interaction/EditInteractor.as b/tinytlf-edit/src/org/tinytlf/interaction/EditInteractor.as new file mode 100644 index 0000000..4df5f62 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/interaction/EditInteractor.as @@ -0,0 +1,89 @@ +package org.tinytlf.interaction +{ + import flash.events.EventDispatcher; + import flash.text.engine.TextLine; + import flash.ui.*; + + import org.tinytlf.operations.ITextOperation; + + public class EditInteractor extends GestureInteractor implements IEditInteractor + { + public function EditInteractor() + { + super(); + + stack = new []; + } + + override public function getMirror(element:* = null):EventDispatcher + { + if(element is TextLine) + { + var line:TextLine = element as TextLine; + line.parent.contextMenu ||= new ContextMenu(); + + var menu:ContextMenu = line.parent.contextMenu; + menu.clipboardMenu = true; + + var items:ContextMenuClipboardItems = menu.clipboardItems; + items.clear = true; + items.copy = true; + items.cut = true; + items.paste = true; + items.selectAll = true; + } + + return super.getMirror(element); + } + + private var stack:Vector.; + private var pointer:int = -1; + + public function push(op:ITextOperation):ITextOperation + { + if(pointer != stack.length - 1) + stack.splice(pointer, stack.length - pointer); + + stack.push(op); + pointer = stack.length; + + return op; + } + + public function undo():ITextOperation + { + if(pointer > 0) + return stack[--pointer]; + + return new NullOperation(); + } + + public function redo():ITextOperation + { + if(pointer < stack.length) + return stack[pointer++]; + + return new NullOperation(); + } + + public function clearOperations(num:int = -1):void + { + if(num <= -1) + stack.length = 0; + else + stack.splice(stack.length - num, num); + + pointer = stack.length - 1; + } + } +} +import org.tinytlf.conversion.IHTMLNode; +import org.tinytlf.operations.ITextOperation; + +internal class NullOperation implements ITextOperation +{ + public function initialize(model:IHTMLNode):ITextOperation{return this;} + public function execute():void{} + public function backout():void{} + public function merge(op:ITextOperation):void{} +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/interaction/IEditInteractor.as b/tinytlf-edit/src/org/tinytlf/interaction/IEditInteractor.as new file mode 100644 index 0000000..2e45c66 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/interaction/IEditInteractor.as @@ -0,0 +1,39 @@ +package org.tinytlf.interaction +{ + import org.tinytlf.operations.ITextOperation; + + public interface IEditInteractor extends ITextInteractor + { + /** + * Pushes an operation onto the operation stack. Does not execute the + * operation. + * + * @returns The ITextOperation that was passed in. + */ + function push(op:ITextOperation):ITextOperation; + + /** + * Decrements the operation stack iterator to the previous operation, + * if it exists. This does not execute the operation. + * + * @returns The previous operation in the operation stack, or a + * NullOperation implementation if there is no previous operation. + */ + function undo():ITextOperation; + + /** + * Increments the operation stack pointer to the next operation, if it + * exists. This does not execute the operation. + * + * @returns The next operation in the operation stack, or a + * NullOperation implementation if there is no next operation. + */ + function redo():ITextOperation; + + /** + * Clears operations off the stack, starting with the most recently + * added. + */ + function clearOperations(num:int = -1):void; + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/operations/CaretMoveOperation.as b/tinytlf-edit/src/org/tinytlf/operations/CaretMoveOperation.as new file mode 100644 index 0000000..f8dabdf --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/operations/CaretMoveOperation.as @@ -0,0 +1,33 @@ +package org.tinytlf.operations +{ + import org.tinytlf.ITextEngine; + + public class CaretMoveOperation extends TextOperation + { + public function CaretMoveOperation(props:Object=null) + { + super(props); + } + + public var caret:int; + public var maxCaret:Number = NaN; + + private var start:int; + + override public function execute():void + { + start = engine.caretIndex; + + if(maxCaret == maxCaret) + caret = Math.min(maxCaret, caret); + + engine.caretIndex = caret; + } + + override public function backout():void + { + caret = engine.caretIndex; + engine.caretIndex = start; + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/operations/CompositeOperation.as b/tinytlf-edit/src/org/tinytlf/operations/CompositeOperation.as new file mode 100644 index 0000000..01c3e62 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/operations/CompositeOperation.as @@ -0,0 +1,74 @@ +package org.tinytlf.operations +{ + import org.tinytlf.conversion.IHTMLNode; + + public class CompositeOperation extends TextOperation + { + public function CompositeOperation(props:Object = null) + { + super(props); + } + + private var operations:Vector. = new []; + public function add(...ops):void + { + operations = operations.concat(Vector.(ops)); + } + + private var enders:Vector. = new []; + public function runAtEnd(...ops):void + { + enders = enders.concat(Vector.(ops)); + } + + override public function initialize(model:IHTMLNode):ITextOperation + { + super.initialize(model); + + operations.forEach(initCallback); + enders.forEach(initCallback); + + return this; + } + + override public function execute():void + { + operations.forEach(eCallback); + enders.forEach(eCallback); + } + + override public function backout():void + { + operations.concat().reverse().forEach(bCallback); + enders.forEach(bCallback); + } + + override public function merge(op:ITextOperation):void + { + super.merge(op); + + var composite:CompositeOperation = CompositeOperation(op); + composite.operations.forEach(mCallback, this); + } + + private function initCallback(op:ITextOperation, ...args):void + { + op.initialize(model) + } + + private function eCallback(op:ITextOperation, ...args):void + { + op.execute(); + } + + private function bCallback(op:ITextOperation, ...args):void + { + op.backout(); + } + + private function mCallback(op:ITextOperation, index:int, ...args):void + { + operations[index].merge(op); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/operations/ITextOperation.as b/tinytlf-edit/src/org/tinytlf/operations/ITextOperation.as new file mode 100644 index 0000000..5cac115 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/operations/ITextOperation.as @@ -0,0 +1,12 @@ +package org.tinytlf.operations +{ + import org.tinytlf.conversion.IHTMLNode; + + public interface ITextOperation + { + function initialize(model:IHTMLNode):ITextOperation; + function execute():void; + function backout():void; + function merge(op:ITextOperation):void; + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/operations/InvalidateEngineOperation.as b/tinytlf-edit/src/org/tinytlf/operations/InvalidateEngineOperation.as new file mode 100644 index 0000000..c0dce19 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/operations/InvalidateEngineOperation.as @@ -0,0 +1,20 @@ +package org.tinytlf.operations +{ + public class InvalidateEngineOperation extends TextOperation + { + public function InvalidateEngineOperation(props:Object=null) + { + super(props); + } + + override public function execute():void + { + engine.invalidate(); + } + + override public function backout():void + { + engine.invalidate(); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/operations/SplitParagraphOperation.as b/tinytlf-edit/src/org/tinytlf/operations/SplitParagraphOperation.as new file mode 100644 index 0000000..d01b6a1 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/operations/SplitParagraphOperation.as @@ -0,0 +1,31 @@ +package org.tinytlf.operations +{ + + public class SplitParagraphOperation extends TextOperation + { + public function SplitParagraphOperation(props:Object = null) + { + super(props); + } + + public var caret:int; + public var nodeIndex:int; + + override public function execute():void + { +// var root:ITLFNodeParent = ITLFNodeParent(model); +// nodeIndex = root.getChildIndexAtPosition(caret); +// var child:ITLFNode = root.getChildAt(nodeIndex); +// child.split(caret - root.getChildPosition(nodeIndex)); +// engine.analytics.addBlockAt(new TextBlock(), nodeIndex + 1, 0); + } + + override public function backout():void + { +// engine.analytics.removeBlockAt(nodeIndex + 1); +// var root:ITLFNodeParent = ITLFNodeParent(model); +// var pos:int = root.getChildPosition(nodeIndex); +// root.merge(pos, pos + 1); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/operations/TextInsertOperation.as b/tinytlf-edit/src/org/tinytlf/operations/TextInsertOperation.as new file mode 100644 index 0000000..85687cb --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/operations/TextInsertOperation.as @@ -0,0 +1,43 @@ +package org.tinytlf.operations +{ + + + public class TextInsertOperation extends TextOperation implements ITextOperation + { + public function TextInsertOperation(props:Object) + { + super(props); + } + + public var start:int; + public var end:int; + public var value:String; + + override public function execute():void + { +// model.insert(value, start); + } + + override public function backout():void + { +// model.remove(start, end); + } + + override public function merge(op:ITextOperation):void + { + super.merge(op); + + var obj:TextInsertOperation = TextInsertOperation(op); + if(obj.start < start) + { + value = obj.value + value; + start = obj.start; + } + else if(obj.start >= start) + { + value += obj.value; + end = obj.end; + } + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/operations/TextOperation.as b/tinytlf-edit/src/org/tinytlf/operations/TextOperation.as new file mode 100644 index 0000000..7414215 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/operations/TextOperation.as @@ -0,0 +1,38 @@ +package org.tinytlf.operations +{ + import org.tinytlf.ITextEngine; + import org.tinytlf.conversion.IHTMLNode; + import org.tinytlf.styles.StyleAwareActor; + + public class TextOperation extends StyleAwareActor implements ITextOperation + { + public function TextOperation(props:Object = null) + { + super(props); + } + + protected var model:IHTMLNode; + protected var engine:ITextEngine; + + public function initialize(model:IHTMLNode):ITextOperation + { + this.model = model; +// this.engine = model.engine; + return this; + } + + public function execute():void + { + } + + public function backout():void + { + } + + public function merge(op:ITextOperation):void + { + if(!(op is (this['constructor'] as Class))) + throw new ArgumentError('Cannot merge operations of different types.'); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/operations/TextRemoveOperation.as b/tinytlf-edit/src/org/tinytlf/operations/TextRemoveOperation.as new file mode 100644 index 0000000..7282ff9 --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/operations/TextRemoveOperation.as @@ -0,0 +1,25 @@ +package org.tinytlf.operations +{ + public class TextRemoveOperation extends TextOperation implements ITextOperation + { + public function TextRemoveOperation(props:Object) + { + super(props); + } + + public var start:int; + public var end:int; +// private var value:ITLFNode; + + override public function execute():void + { +// value = model.clone(start, end); +// model.remove(start, end) + } + + override public function backout():void + { +// model.insert(value.text, start); + } + } +} \ No newline at end of file diff --git a/tinytlf-edit/src/org/tinytlf/operations/TextSelectionOperation.as b/tinytlf-edit/src/org/tinytlf/operations/TextSelectionOperation.as new file mode 100644 index 0000000..87171ad --- /dev/null +++ b/tinytlf-edit/src/org/tinytlf/operations/TextSelectionOperation.as @@ -0,0 +1,29 @@ +package org.tinytlf.operations +{ + import flash.geom.Point; + + import org.tinytlf.ITextEngine; + + public class TextSelectionOperation extends TextOperation + { + public function TextSelectionOperation(props:Object=null) + { + super(props); + } + + public var selection:Point; + private var start:Point; + + override public function execute():void + { + start = engine.selection.clone(); + engine.select(selection ? selection.x : NaN, selection ? selection.y : NaN); + } + + override public function backout():void + { + selection = engine.selection.clone(); + engine.select(start ? start.x : NaN, start ? start.y : NaN); + } + } +} \ No newline at end of file diff --git a/tinytlf-fte-utils/src/org/tinytlf/util/fte/ContentElementUtil.as b/tinytlf-fte-utils/src/org/tinytlf/util/fte/ContentElementUtil.as new file mode 100755 index 0000000..7176639 --- /dev/null +++ b/tinytlf-fte-utils/src/org/tinytlf/util/fte/ContentElementUtil.as @@ -0,0 +1,269 @@ +package org.tinytlf.util.fte +{ + import flash.display.Bitmap; + import flash.display.Shape; + import flash.geom.Rectangle; + import flash.text.engine.*; + + public final class ContentElementUtil + { + /** + * Returns all the lines that render the supplied ContentElement. + */ + public static function getTextLines(element:ContentElement):Vector. + { + var lines:Vector. = new Vector.(); + var block:TextBlock = element.textBlock; + + if(!block) + return lines; + + var endIndex:int = element.textBlockBeginIndex + element.rawText.length; + var line:TextLine = block.getTextLineAtCharIndex(element.textBlockBeginIndex); + + while(line && line.textBlockBeginIndex < endIndex) + { + lines.push(line); + line = line.nextLine; + } + + return lines; + } + + /** + * Returns a Vector of TextLineMirrorRegions for the ContentElement. + */ + public static function getMirrorRegions(element:ContentElement):Vector. + { + var lines:Vector. = getTextLines(element); + var line:TextLine; + + var regions:Vector. = new Vector.(); + var tlmrs:Vector.; + var tlmr:TextLineMirrorRegion; + + while(lines.length) + { + line = lines.pop(); + tlmrs = line.mirrorRegions; + + if(line.validity != TextLineValidity.VALID || !tlmrs) + continue; + + tlmrs = tlmrs.concat(); + + while(tlmrs.length) + { + tlmr = tlmrs.pop(); + if(tlmr.mirror === element.eventMirror) + regions.push(tlmr); + } + } + + return regions; + } + + /** + * Returns a Vector of Rectangles that represent the area in which the + * ContentElement exists on the Stage. This will only return properly + * if the ContentElement has an eventMirror set. If the ContentElement + * has no eventMirror, FTE doesn't create TextLineMirrorRegions, and + * it's impossible to determine the boundaries of the ContentElement. + */ + public static function getBounds(element:ContentElement):Vector. + { + var regions:Vector. = getMirrorRegions(element); + var bounds:Vector. = new Vector.(); + var tlmr:TextLineMirrorRegion; + var rect:Rectangle; + + while(regions.length) + { + tlmr = regions.pop(); + rect = tlmr.bounds.clone(); + rect.offset(tlmr.textLine.x, tlmr.textLine.y); + bounds.push(rect); + } + + return bounds; + } + + public static function attachLineBreak(element:ContentElement):GroupElement + { + var graphic:GraphicElement = getLineBreakGraphic('lineBreak', 0xFF0000); + return new GroupElement(new [element, graphic], new ElementFormat()); + } + + /** + * Creates a GroupElement with two children, a place-holder GraphicElement + * and the input ContentElement. The GroupElement has an elementFormat + * with breakOpportunity set to "all," which tells the FTE to break + * the line between the GroupElement's children, i.e. between the + * placeholder GraphicElement and the input ContentElement. + * + *

    The optional marker parameter is set as the userData property of + * the dummy GraphicElement. This allows you to mark/differentiate this + * graphic during layout.

    + */ + public static function lineBreakBefore(element:ContentElement, marker:Object = null):GroupElement + { + var graphic:GraphicElement = getLineBreakGraphic(marker, 0xFF0000); + return new GroupElement(new [graphic, element], breakAllEF); + } + + /** + * Creates a GroupElement which has a line break after the input + * ContentElement. + */ + public static function lineBreakAfter(element:ContentElement, marker:Object = null):GroupElement + { + var graphic:GraphicElement = getLineBreakGraphic(marker, 0x0000FF); + return new GroupElement(new [element, graphic], breakAllEF); + } + + /** + * Creates a GroupElement which has line breaks before and after the + * input ContentElement. + */ + public static function lineBreakBeforeAndAfter(element:ContentElement, + markerLeft:Object = null, + markerRight:Object = null):GroupElement + { + var start:GraphicElement = getLineBreakGraphic(markerLeft, 0xFF0000); + var end:GraphicElement = getLineBreakGraphic(markerRight, 0x0000FF); + return new GroupElement(new [start, element, end], breakAllEF); + } + + private static function get breakAllEF():ElementFormat + { + var ef:ElementFormat = new ElementFormat(); + ef.breakOpportunity = BreakOpportunity.ALL; + return ef; + } + + private static function getLineBreakGraphic(marker:Object = null, color:uint = 0xFFFFFF):GraphicElement + { + var ef:ElementFormat = new ElementFormat(); + ef.dominantBaseline = TextBaseline.IDEOGRAPHIC_TOP; + + var s:Shape = new Shape(); +// s.graphics.beginFill(color); +// s.graphics.drawRect(0, 0, 10, 2); +// s.graphics.endFill(); + + var g:GraphicElement = new GraphicElement(s, 0, 0, ef); + g.userData = marker; + + return g; + } + + public static function dumpElement(e:ContentElement, depth:int = 0):String + { + var str:String = ''; + var tabs:String = ''; + var j:int = depth; + + while(j-- > 0) + { + tabs += '\t'; + } + str += tabs; + + if(e is GroupElement) + { + ++depth; + + str += 'GroupElement(\n'; + var n:int = GroupElement(e).elementCount; + for(var i:int = 0; i < n; ++i) + { + str += dumpElement(GroupElement(e).getElementAt(i), depth); + } + str += tabs + ')'; + } + else if(e is TextElement) + { + str += 'TextElement("' + TextElement(e).text + '")'; + } + else if(e is GraphicElement) + { + str += 'GraphicElement()'; + } + + return str + '\n'; + } + + public static function addChild(parent:ContentElement, child:ContentElement):ContentElement + { + if(!(parent is GroupElement)) + return child; + + var group:GroupElement = GroupElement(parent); + return addChildAt(group, child, group.elementCount); + } + + public static function addChildAt(parent:ContentElement, child:ContentElement, index:int):ContentElement + { + if(!(parent is GroupElement)) + return child; + + var group:GroupElement = GroupElement(parent); + var elements:Vector. = getChildren(group); + + if(elements.indexOf(child) != -1) + removeChild(group, child); + + elements.splice(index, 0, child); + group.setElements(elements); + + return child; + } + + public static function removeChild(parent:ContentElement, child:ContentElement):ContentElement + { + if(!(parent is GroupElement)) + return child; + + var group:GroupElement = GroupElement(parent); + return removeChildAt(group, group.getElementIndex(child)); + } + + public static function removeChildAt(parent:ContentElement, index:int):ContentElement + { + if(!(parent is GroupElement)) + return null; + + var group:GroupElement = GroupElement(parent); + var child:ContentElement = group.getElementAt(index); + group.replaceElements(index, index + 1, null); + + return child; + } + + public static function removeChildren(parent:ContentElement):Vector. + { + if(!(parent is GroupElement)) + return null; + + var group:GroupElement = GroupElement(parent); + var children:Vector. = new []; + + while(group.elementCount) + children.push(removeChildAt(group, group.elementCount)); + + return children; + } + + public static function getChildren(group:GroupElement):Vector. + { + var n:int = group.elementCount; + var elements:Vector. = new []; + for(var i:int = 0; i < n; i += 1) + { + elements.push(group.getElementAt(i)); + } + + return elements; + } + } +} \ No newline at end of file diff --git a/tinytlf-fte-utils/src/org/tinytlf/util/fte/TextBlockUtil.as b/tinytlf-fte-utils/src/org/tinytlf/util/fte/TextBlockUtil.as new file mode 100644 index 0000000..749a1c3 --- /dev/null +++ b/tinytlf-fte-utils/src/org/tinytlf/util/fte/TextBlockUtil.as @@ -0,0 +1,37 @@ +package org.tinytlf.util.fte +{ + import flash.text.engine.TextBlock; + import flash.text.engine.TextLineCreationResult; + + public final class TextBlockUtil + { + public static function isInvalid(block:TextBlock):Boolean + { + return block.firstLine == null || + block.firstInvalidLine || + block.textLineCreationResult != TextLineCreationResult.COMPLETE; + } + + public static function cleanBlock(block:TextBlock):void + { + if(block.firstLine) + block.releaseLines(block.firstLine, block.lastLine); + + block.releaseLineCreationData(); + block.content = null; + block.userData = null; + } + + private static const blocks:Vector. = new []; + public static function checkIn(block:TextBlock):void + { + cleanBlock(block); + blocks.push(block); + } + + public static function checkOut():TextBlock + { + return blocks.pop() || new TextBlock(); + } + } +} \ No newline at end of file diff --git a/tinytlf-fte-utils/src/org/tinytlf/util/fte/TextLineUtil.as b/tinytlf-fte-utils/src/org/tinytlf/util/fte/TextLineUtil.as new file mode 100755 index 0000000..e5d4971 --- /dev/null +++ b/tinytlf-fte-utils/src/org/tinytlf/util/fte/TextLineUtil.as @@ -0,0 +1,215 @@ +package org.tinytlf.util.fte +{ + import flash.display.DisplayObject; + import flash.geom.*; + import flash.text.engine.*; + import flash.utils.Dictionary; + + public final class TextLineUtil + { + /** + * Returns the index of the atom at a particular point. If the point + * is outside the boundaries of the line, this determines which side the + * point is on, and returns 0 or line.atomCount. + */ + public static function getAtomIndexAtPoint(line:TextLine, stageCoords:Point):int + { + var index:int = line.getAtomIndexAtPoint(stageCoords.x, stageCoords.y); + + if(index < 0) + { + var bounds:Rectangle = line.getBounds(line.stage); + var center:Point = bounds.topLeft.clone(); + center.offset(bounds.width * .5, bounds.height * .5); + + if(stageCoords.y < bounds.y) + return 0; + if(stageCoords.y > bounds.y && + stageCoords.y < bounds.y + bounds.height) + return line.atomCount; + + index = (stageCoords.x < center.x) ? 0 : line.atomCount; + } + + var atomIncrement:int = getAtomSide(line, stageCoords) ? 0 : 1; + + return Math.max(index + atomIncrement, 0); + } + + /** + * Finds which side of the atom the point is on. + * @returns true for left, false for right. + */ + public static function getAtomSide(line:TextLine, stageCoords:Point):Boolean + { + var atomIndex:int = line.getAtomIndexAtPoint(stageCoords.x, stageCoords.y); + + if(atomIndex < 0) + return true; + + var center:Number = line.getAtomCenter(atomIndex); + var pt:Point = line.localToGlobal(new Point(center)); + + return pt.x > stageCoords.x; + } + + private static const defaultWordBoundaryPattern:RegExp = /\W+|\b[^\Wï·¯]*/; + private static const nonWordPattern:RegExp = /\W/; + + /** + * Finds the next/prev word boundary specified by the direction and the + * boundaryPattern. If no boundary pattern is specified, the default + * is used, which matches non-word characters or graphic characters. + */ + public static function getAtomWordBoundary(line:TextLine, atomIndex:int, + left:Boolean = true, boundaryPattern:RegExp = null):int + { + if(!boundaryPattern) + boundaryPattern = defaultWordBoundaryPattern; + + if(atomIndex >= line.atomCount) + atomIndex = line.atomCount - 1; + else if(atomIndex < 0) + atomIndex = 0; + + var rawText:String = line.textBlock.content.rawText; + var adjustedIndex:int = line.getAtomTextBlockBeginIndex(atomIndex); + + // If the index is already at a word boundary, + // move to find the next word boundary. + while(nonWordPattern.test(rawText.charAt(adjustedIndex))) + { + adjustedIndex += left ? -1 : 1; + atomIndex += left ? -1 : 1; + } + + var text:String = left ? + rawText.slice(0, adjustedIndex).split("").reverse().join("") : + rawText.slice(adjustedIndex, rawText.length); + + var match:Array = boundaryPattern.exec(text); + if(match) + { + var str:String = String(match[0]); + atomIndex += nonWordPattern.test(str) ? 0 : str.length * (left ? -1 : 1); + } + + return Math.max(atomIndex, 0); + } + + /** + * Recursively drills down into the ContentElement of the TextLine's + * TextBlock to return the leaf element at the specified atomIndex. + */ + public static function getElementAtAtomIndex(line:TextLine, atomIndex:int):ContentElement + { + if(atomIndex < 0) + return null; + + var block:TextBlock = line.textBlock; + var blockBeginIndex:int = line.textBlockBeginIndex; + var content:ContentElement = block.content; + var charIndex:int = blockBeginIndex - content.textBlockBeginIndex + atomIndex; + + // If you recycle TextBlocks, funky things will happen. + // For example, the blockBeginIndex here will report a value of + // 0xFFFFFFFF, obviously erroneous. We have a second check here to + // stop this from throwing RTEs, but honestly, it's likely you'll + // just get an RTE somewhere else, because the returned + // contentElement will be a GroupElement here instead of a + // TextElement or GraphicElement like you were expecting. + // .poop. + + while(content is GroupElement && charIndex < block.content.rawText.length) + { + content = GroupElement(content).getElementAtCharIndex(charIndex); + charIndex = blockBeginIndex - content.textBlockBeginIndex + atomIndex; + } + + return content; + } + + /** + * Returns a Vector of ContentElements which are rendered in this + * TextLine. This can only return the elements that have specified + * eventMirrors, so it's not guaranteed to be every ContentElement, + * and the elements won't necessarily be in order. + */ + public static function getContentElements(line:TextLine):Vector. + { + var dict:Dictionary = new Dictionary(); + var tlmrs:Vector. = line.mirrorRegions; + + if(!tlmrs) + return elements; + + var n:int = tlmrs.length; + + for(var i:int = 0; i < n; ++i) + { + dict[tlmrs[i].element] = true; + } + + var elements:Vector. = new []; + for(var element:* in dict) + { + elements.push(ContentElement(element)); + } + + return elements; + } + + public static function getMirrorRegionForElement(line:TextLine, element:ContentElement):TextLineMirrorRegion + { + if(!line.mirrorRegions) + return null; + + var regions:Vector. = line.mirrorRegions; + var region:TextLineMirrorRegion; + var n:int = regions.length; + + for(var i:int = 0; i < n; i += 1) + { + region = regions[i]; + if(region.element === element) + return region; + } + + return null; + } + + public static function hasLineBreak(line:TextLine):Boolean + { + if(line.atomCount <= 1) + return false; + + //Check to see if we have a line break graphic at the end of the TextLine + var graphicIndex:int = line.atomCount - 1; + var dObj:DisplayObject = line.getAtomGraphic(graphicIndex); + if(!dObj) + return false; + + //We have some kind of graphic at the end, is it a line break? + var g:GraphicElement = GraphicElement(getElementAtAtomIndex(line, graphicIndex)); + return g.userData === 'lineBreak'; + } + + public static function cleanLine(line:TextLine):void + { + line.userData = null; + } + + private static const lines:Vector. = new []; + + public static function checkIn(line:TextLine):void + { + cleanLine(line); + lines.push(line); + } + + public static function checkOut():TextLine + { + return lines.pop(); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/CharacterSelectionBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/CharacterSelectionBehavior.as new file mode 100644 index 0000000..22861a0 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/CharacterSelectionBehavior.as @@ -0,0 +1,7 @@ +package org.tinytlf.behaviors +{ + public class CharacterSelectionBehavior extends MouseSelectionBehavior + { + //MouseSelectionBehavior is the character selection implementation. + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/FocusBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/FocusBehavior.as new file mode 100644 index 0000000..e416246 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/FocusBehavior.as @@ -0,0 +1,26 @@ +/* +* Copyright (c) 2010 the original author or authors +* +* Permission is hereby granted to use, modify, and distribute this file +* in accordance with the terms of the license agreement accompanying it. +*/ +package org.tinytlf.behaviors +{ + import flash.events.Event; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + + + public class FocusBehavior extends MultiGestureBehavior + { + [Event("keyDown")] + [Event("mouseDown")] + [Event("mouseMove")] + [Event("click")] + [Event("doubleClick")] + public function setFocus():void + { + line.stage.focus = container.target; + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/IBeamBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/IBeamBehavior.as new file mode 100755 index 0000000..969b701 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/IBeamBehavior.as @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010 the original author or authors + * + * Permission is hereby granted to use, modify, and distribute this file + * in accordance with the terms of the license agreement accompanying it. + */ +package org.tinytlf.behaviors +{ + import flash.events.MouseEvent; + import flash.ui.Mouse; + import flash.ui.MouseCursor; + + + public class IBeamBehavior extends MultiGestureBehavior + { + [Event("rollOver")] + [Event("mouseOver")] + [Event("mouseMove")] + public function setIBeam():void + { + if(mirrorRegion) + return; + + Mouse.cursor = MouseCursor.IBEAM; + } + + [Event("rollOut")] + public function setAuto():void + { + Mouse.cursor = MouseCursor.AUTO; + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/LineSelectionBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/LineSelectionBehavior.as new file mode 100644 index 0000000..367fc0c --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/LineSelectionBehavior.as @@ -0,0 +1,26 @@ +package org.tinytlf.behaviors +{ + import flash.geom.Point; + import flash.text.engine.*; + + import org.tinytlf.util.TinytlfUtil; + + public class LineSelectionBehavior extends MouseSelectionBehavior + { + [Event("mouseDown")] + override public function downAction():void + { + super.downAction(); + + engine.select(anchor.x, anchor.y); + } + + override protected function getAnchor():Point + { + var start:int = TinytlfUtil.atomIndexToGlobalIndex(engine, line, 0); + var end:int = start + line.atomCount; + + return new Point(start, end); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/MouseSelectionBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/MouseSelectionBehavior.as new file mode 100644 index 0000000..8878a08 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/MouseSelectionBehavior.as @@ -0,0 +1,58 @@ +package org.tinytlf.behaviors +{ + import flash.events.MouseEvent; + import flash.geom.Point; + + import org.tinytlf.util.TinytlfUtil; + import org.tinytlf.util.fte.TextLineUtil; + + public class MouseSelectionBehavior extends SelectionBehaviorBase + { + public function MouseSelectionBehavior() + { + super(); + } + + protected var triggeredHere:Boolean = false; + + [Event("mouseDown")] + override public function downAction():void + { + super.downAction(); + + engine.select(); + + triggeredHere = true; + } + + [Event("mouseMove")] + override public function moveAction():void + { + var m:MouseEvent = MouseEvent(finalEvent); + if(!m.buttonDown || !triggeredHere) + return; + + super.moveAction(); + } + + [Event("mouseUp")] + override public function upAction():void + { + super.upAction(); + triggeredHere = false; + } + + override protected function getAnchor():Point + { + var m:MouseEvent = MouseEvent(finalEvent); + var atomIndex:int = TextLineUtil.getAtomIndexAtPoint(line, new Point(m.stageX, m.stageY)); + var globalIndex:int = TinytlfUtil.atomIndexToGlobalIndex(engine, line, atomIndex); + return new Point(globalIndex, globalIndex); + } + + override protected function getSelection():Point + { + return getAnchor(); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/MultiGestureBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/MultiGestureBehavior.as new file mode 100644 index 0000000..c88d5ef --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/MultiGestureBehavior.as @@ -0,0 +1,66 @@ +package org.tinytlf.behaviors +{ + import flash.events.Event; + import flash.geom.Point; + + import org.tinytlf.util.TinytlfUtil; + + public class MultiGestureBehavior extends TinytlfBehavior + { + public function MultiGestureBehavior() + { + super(); + + attachListeners(this); + } + + public function attachListeners(target:IBehavior):void + { + var listeners:XMLList = TinytlfUtil.describeType(this).factory.method.(child('metadata').(@name == 'Event').length()); + + var methodName:String; + var type:String; + var events:XMLList; + + for each(var listener:XML in listeners) + { + methodName = listener.attribute('name').toString(); + events = listener.metadata.(@name == 'Event'); + for each(var meta:XML in events) + { + type = meta.arg.@value.toString(); + types[type] = this[methodName]; + } + } + } + + protected var selection:Point; + protected var caret:int; + + override protected function act(events:Vector.):void + { + super.act(events); + + selection = engine.selection.clone(); + caret = engine.caretIndex; + + var type:String = finalEvent.type; + if(type in types) + types[type].length ? types[type](events) : types[type](); + else + defaultAction(events); + } + + private const types:Object = {}; + + protected function defaultAction(events:Vector.):void + { +// trace('default action occurred on ' + this['constructor'].toString()); + } + + protected function get validSelection():Boolean + { + return selection.x == selection.x && selection.y == selection.y; + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/ParagraphSelectionBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/ParagraphSelectionBehavior.as new file mode 100644 index 0000000..62a68d0 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/ParagraphSelectionBehavior.as @@ -0,0 +1,28 @@ +package org.tinytlf.behaviors +{ + import flash.geom.Point; + import flash.text.engine.TextBlock; + + import org.tinytlf.analytics.IVirtualizer; + + public class ParagraphSelectionBehavior extends MouseSelectionBehavior + { + [Event("mouseDown")] + override public function downAction():void + { + super.downAction(); + + engine.select(anchor.x, anchor.y); + } + + override protected function getAnchor():Point + { + var textBlockVirtualizer:IVirtualizer = engine.layout.textBlockVirtualizer; + var contentVirtualizer:IVirtualizer = engine.blockFactory.contentVirtualizer; + var block:TextBlock = line.textBlock; + var begin:int = contentVirtualizer.getItemStart(block); + var end:int = begin + contentVirtualizer.getItemSize(block); + return new Point(begin, end); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/ScrollBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/ScrollBehavior.as new file mode 100644 index 0000000..12f3035 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/ScrollBehavior.as @@ -0,0 +1,18 @@ +package org.tinytlf.behaviors +{ + import flash.events.*; + + public class ScrollBehavior extends MultiGestureBehavior + { + public function ScrollBehavior() + { + super(); + } + + [Event("mouseWheel")] + public function scrollWheel(events:Vector.):void + { + engine.scrollPosition -= (MouseEvent(finalEvent).delta * 3); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/SelectAllBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/SelectAllBehavior.as new file mode 100644 index 0000000..7ff65ef --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/SelectAllBehavior.as @@ -0,0 +1,14 @@ +package org.tinytlf.behaviors +{ + import org.tinytlf.analytics.IVirtualizer; + + public class SelectAllBehavior extends SelectionBehaviorBase + { + [Event("keyDown")] + override public function downAction():void + { + var contentVirtualizer:IVirtualizer = engine.blockFactory.contentVirtualizer; + engine.select(0, contentVirtualizer.size); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/SelectionBehaviorBase.as b/tinytlf-gestures/src/org/tinytlf/behaviors/SelectionBehaviorBase.as new file mode 100644 index 0000000..957db67 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/SelectionBehaviorBase.as @@ -0,0 +1,58 @@ +package org.tinytlf.behaviors +{ + import flash.events.MouseEvent; + import flash.geom.Point; + + public class SelectionBehaviorBase extends MultiGestureBehavior + { + public function SelectionBehaviorBase() + { + super(); + } + + public function downAction():void + { + anchor = getAnchor(); + engine.caretIndex = anchor.y; + } + + public function moveAction():void + { + var end:Point = getSelection(); + + if(anchor.x > end.x) + { + engine.select(end.x, anchor.y); + engine.caretIndex = end.x; + } + else if(anchor.x < end.x) + { + engine.select(anchor.x, end.y); + engine.caretIndex = end.y; + } + else if(anchor.x == end.x) + { + engine.select(anchor.x, anchor.y); + engine.caretIndex = anchor.x; + } + } + + public function upAction():void + { + anchor.x = 0; + anchor.y = 0; + } + + protected var anchor:Point = new Point(); + + protected function getAnchor():Point + { + return new Point(); + } + + protected function getSelection():Point + { + return new Point(); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/TinytlfBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/TinytlfBehavior.as new file mode 100644 index 0000000..bccb541 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/TinytlfBehavior.as @@ -0,0 +1,39 @@ +package org.tinytlf.behaviors +{ + import flash.events.Event; + import flash.text.engine.*; + + import org.tinytlf.ITextEngine; + import org.tinytlf.interaction.EventLineInfo; + import org.tinytlf.layout.ITextContainer; + + public class TinytlfBehavior extends Behavior + { + protected var engine:ITextEngine; + protected var line:TextLine; + protected var container:ITextContainer; + protected var contentElement:ContentElement; + protected var mirrorRegion:TextLineMirrorRegion; + + override public function activate(events:Vector.):void + { + if(events.length <= 0) + return; + + finalEvent = events[events.length - 1]; + + const info:EventLineInfo = EventLineInfo.getInfo(finalEvent); + + if(!info) + return; + + engine = info.engine; + line = info.line; + container = info.container; + mirrorRegion = info.mirrorRegion; + contentElement = info.element; + + act(events); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/behaviors/WordSelectionBehavior.as b/tinytlf-gestures/src/org/tinytlf/behaviors/WordSelectionBehavior.as new file mode 100644 index 0000000..750787e --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/behaviors/WordSelectionBehavior.as @@ -0,0 +1,31 @@ +package org.tinytlf.behaviors +{ + import flash.events.MouseEvent; + import flash.geom.Point; + + import org.tinytlf.util.TinytlfUtil; + import org.tinytlf.util.fte.TextLineUtil; + + public class WordSelectionBehavior extends MouseSelectionBehavior + { + [Event("mouseDown")] + override public function downAction():void + { + super.downAction(); + + engine.caretIndex = anchor.y; + engine.select(anchor.x, anchor.y); + } + + override protected function getAnchor():Point + { + var m:MouseEvent = MouseEvent(finalEvent); + var atomIndex:int = TextLineUtil.getAtomIndexAtPoint(line, new Point(m.stageX, m.stageY)); + var begin:int = TextLineUtil.getAtomWordBoundary(line, atomIndex); + var end:int = TextLineUtil.getAtomWordBoundary(line, atomIndex, false) - 1; + + return new Point(TinytlfUtil.atomIndexToGlobalIndex(engine, line, begin), + TinytlfUtil.atomIndexToGlobalIndex(engine, line, end)); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/gestures/CopyGesture.as b/tinytlf-gestures/src/org/tinytlf/gestures/CopyGesture.as new file mode 100755 index 0000000..157ce76 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/gestures/CopyGesture.as @@ -0,0 +1,21 @@ +package org.tinytlf.gestures +{ + import flash.events.Event; + + [Event("copy")] + + public class CopyGesture extends Gesture + { + public function CopyGesture() + { + super(); + + hsm.appendChild(); + } + + public function copy(event:Event):Boolean + { + return (event.type === Event.COPY); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/gestures/KonamiCodeGesture.as b/tinytlf-gestures/src/org/tinytlf/gestures/KonamiCodeGesture.as new file mode 100755 index 0000000..4d96f82 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/gestures/KonamiCodeGesture.as @@ -0,0 +1,94 @@ +package org.tinytlf.gestures +{ + import flash.events.KeyboardEvent; + import flash.ui.Keyboard; + + + [Event("keyUp")] + public class KonamiCodeGesture extends Gesture + { + public function KonamiCodeGesture() + { + super(); + + hsm.appendChild( + + + + + + + + + + + + + + + + + + + + + + ); + } + + public function up(e:KeyboardEvent):Boolean + { + return e.keyCode === Keyboard.UP; + } + + public function up2(e:KeyboardEvent):Boolean + { + return up(e); + } + + public function down(e:KeyboardEvent):Boolean + { + return e.keyCode === Keyboard.DOWN; + } + + public function down2(e:KeyboardEvent):Boolean + { + return down(e); + } + + public function left(e:KeyboardEvent):Boolean + { + return e.keyCode == Keyboard.LEFT; + } + + public function left2(e:KeyboardEvent):Boolean + { + return left(e); + } + + public function right(e:KeyboardEvent):Boolean + { + return e.keyCode == Keyboard.RIGHT; + } + + public function right2(e:KeyboardEvent):Boolean + { + return right(e); + } + + public function b(e:KeyboardEvent):Boolean + { + return String.fromCharCode(e.charCode) === 'b'; + } + + public function a(e:KeyboardEvent):Boolean + { + return String.fromCharCode(e.charCode) === 'a'; + } + + public function enter(e:KeyboardEvent):Boolean + { + return e.keyCode == Keyboard.ENTER; + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/gestures/MouseClickGesture.as b/tinytlf-gestures/src/org/tinytlf/gestures/MouseClickGesture.as new file mode 100755 index 0000000..d029773 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/gestures/MouseClickGesture.as @@ -0,0 +1,50 @@ +package org.tinytlf.gestures +{ + import flash.events.MouseEvent; + + + [Event("mouseDown")] + [Event("mouseUp")] + [Event("mouseMove")] + [Event("click")] + [Event("doubleClick")] + + public class MouseClickGesture extends Gesture + { + public function MouseClickGesture() + { + super(); + + hsm.appendChild(); + hsm.appendChild(); + hsm.appendChild(); + hsm.appendChild(); + hsm.appendChild(); + } + + public function click(event:MouseEvent):Boolean + { + return event.type == MouseEvent.CLICK; + } + + public function doubleClick(event:MouseEvent):Boolean + { + return event.type == MouseEvent.DOUBLE_CLICK; + } + + public function down(event:MouseEvent):Boolean + { + return event.type == MouseEvent.MOUSE_DOWN; + } + + public function up(event:MouseEvent):Boolean + { + return event.type == MouseEvent.MOUSE_UP; + } + + public function drag(event:MouseEvent):Boolean + { + return event.type == MouseEvent.MOUSE_MOVE && event.buttonDown; + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/gestures/MouseDoubleDownGesture.as b/tinytlf-gestures/src/org/tinytlf/gestures/MouseDoubleDownGesture.as new file mode 100755 index 0000000..028d77d --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/gestures/MouseDoubleDownGesture.as @@ -0,0 +1,62 @@ +package org.tinytlf.gestures +{ + import flash.events.MouseEvent; + import flash.utils.getTimer; + + + [Event("mouseDown")] + [Event("mouseUp")] + [Event("mouseMove")] + + public class MouseDoubleDownGesture extends Gesture + { + public function MouseDoubleDownGesture() + { + super(); + + hsm.appendChild( + + + + + ); + + hsm.appendChild(); + } + + public function drag(event:MouseEvent):Boolean + { + return go && event.buttonDown && event.type == MouseEvent.MOUSE_MOVE; + } + + public function down(event:MouseEvent):Boolean + { + return event.type == MouseEvent.MOUSE_DOWN; + } + + private var go:Boolean = false; + + public function down2(event:MouseEvent):Boolean + { + go = (getTimer() - upTime) < 400; + + var doIt:Boolean = go && event.type == MouseEvent.MOUSE_DOWN; + + return doIt; + } + + private var upTime:int = 0; + + public function up(event:MouseEvent):Boolean + { + go = false; + upTime = getTimer(); + return event.type == MouseEvent.MOUSE_UP; + } + + override protected function testNotifiable(state:XML):Boolean + { + return super.testNotifiable(state) && go; + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/gestures/MouseOutGesture.as b/tinytlf-gestures/src/org/tinytlf/gestures/MouseOutGesture.as new file mode 100755 index 0000000..7e4817a --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/gestures/MouseOutGesture.as @@ -0,0 +1,22 @@ +package org.tinytlf.gestures +{ + import flash.events.MouseEvent; + + + [Event("rollOut")] + + public class MouseOutGesture extends Gesture + { + public function MouseOutGesture() + { + super(); + + hsm.appendChild(); + } + + public function out(event:MouseEvent):Boolean + { + return event.type == MouseEvent.ROLL_OUT; + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/gestures/MouseOverGesture.as b/tinytlf-gestures/src/org/tinytlf/gestures/MouseOverGesture.as new file mode 100755 index 0000000..86ac784 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/gestures/MouseOverGesture.as @@ -0,0 +1,28 @@ +package org.tinytlf.gestures +{ + import flash.events.MouseEvent; + + + [Event("mouseMove")] + [Event("mouseOver")] + [Event("rollOver")] + + public class MouseOverGesture extends Gesture + { + public function MouseOverGesture() + { + super(); + + hsm.appendChild(); + } + + public function move(event:MouseEvent):Boolean + { + return ( + event.type == MouseEvent.ROLL_OVER || + event.type == MouseEvent.MOUSE_MOVE || + event.type == MouseEvent.MOUSE_OVER + ); + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/gestures/MouseTripleDownGesture.as b/tinytlf-gestures/src/org/tinytlf/gestures/MouseTripleDownGesture.as new file mode 100755 index 0000000..20a2dc7 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/gestures/MouseTripleDownGesture.as @@ -0,0 +1,66 @@ +package org.tinytlf.gestures +{ + import flash.events.MouseEvent; + import flash.utils.getTimer; + + + [Event("mouseDown")] + [Event("mouseUp")] + [Event("mouseMove")] + + public class MouseTripleDownGesture extends Gesture + { + public function MouseTripleDownGesture() + { + super(); + + hsm.appendChild( + + + + + + + + + ); + hsm.appendChild(); + } + + public function drag(event:MouseEvent):Boolean + { + return go && event.buttonDown && event.type == MouseEvent.MOUSE_MOVE; + } + + public function down(event:MouseEvent):Boolean + { + return event.type == MouseEvent.MOUSE_DOWN; + } + + private var go:Boolean = false; + + public function down3(event:MouseEvent):Boolean + { + go = (getTimer() - upTime) < 500; + upTime = 0; + + resetStates(); + + return go && event.type == MouseEvent.MOUSE_DOWN; + } + + private var upTime:int = 0; + + public function up(event:MouseEvent):Boolean + { + go = false; + upTime = getTimer(); + return event.type == MouseEvent.MOUSE_UP; + } + + override protected function testNotifiable(state:XML):Boolean + { + return super.testNotifiable(state) && go; + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/gestures/MouseWheelGesture.as b/tinytlf-gestures/src/org/tinytlf/gestures/MouseWheelGesture.as new file mode 100644 index 0000000..adc77d9 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/gestures/MouseWheelGesture.as @@ -0,0 +1,20 @@ +package org.tinytlf.gestures +{ + import flash.events.MouseEvent; + + + [Event("mouseWheel")] + public class MouseWheelGesture extends Gesture + { + public function MouseWheelGesture() + { + super(); + hsm.appendChild() + } + + public function wheel(event:MouseEvent):Boolean + { + return event.type == MouseEvent.MOUSE_WHEEL; + } + } +} \ No newline at end of file diff --git a/tinytlf-gestures/src/org/tinytlf/gestures/SelectAllGesture.as b/tinytlf-gestures/src/org/tinytlf/gestures/SelectAllGesture.as new file mode 100755 index 0000000..4300bd6 --- /dev/null +++ b/tinytlf-gestures/src/org/tinytlf/gestures/SelectAllGesture.as @@ -0,0 +1,21 @@ +package org.tinytlf.gestures +{ + import flash.events.Event; + + + [Event("selectAll")] + public class SelectAllGesture extends Gesture + { + public function SelectAllGesture() + { + super(); + + hsm.appendChild(