blob: 6f99257cf574bee206bc26ea342e953a3d8f5f24 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package flashx.textLayout.property
{
import flashx.textLayout.debug.assert;
import flashx.textLayout.elements.GlobalSettings;
import flashx.textLayout.formats.FormatValue;
import flashx.textLayout.formats.ListMarkerFormat;
import flashx.textLayout.formats.TextLayoutFormat;
import flashx.textLayout.tlf_internal;
use namespace tlf_internal;
[ExcludeClass]
/** Base class of property metadata. Each property in the various TextLayout attributes structures has a metadata singletion Property class instance. The instance
* can be used to process the property to and from xml, find out range information and help with the attribute cascade. The Property class also contains static functions
* for processing all the properties collected in a TextLayout Format object. @private */
public class Property
{
public static var errorHandler:Function = defaultErrorHandler;
public static function defaultErrorHandler(p:Property,value:Object):void
{
throw(new RangeError(createErrorString(p,value)));
}
public static function createErrorString(p:Property,value:Object):String
{
return GlobalSettings.resourceStringFunction("badPropertyValue",[ p.name, value.toString() ])
}
// shared propertyHandler instances
/** @private */
tlf_internal static const sharedStringHandler:StringPropertyHandler = new StringPropertyHandler();
/** @private */
tlf_internal static const sharedInheritEnumHandler:EnumPropertyHandler = new EnumPropertyHandler([ FormatValue.INHERIT ]);
/** @private */
tlf_internal static const sharedUndefinedHandler:UndefinedPropertyHandler = new UndefinedPropertyHandler();
/** @private */
tlf_internal static const sharedUintHandler:UintPropertyHandler = new UintPropertyHandler();
/** @private */
tlf_internal static const sharedBooleanHandler:BooleanPropertyHandler = new BooleanPropertyHandler();
/** @private */
tlf_internal static const sharedTextLayoutFormatHandler:FormatPropertyHandler = new FormatPropertyHandler();
/** @private */
tlf_internal static const sharedListMarkerFormatHandler:FormatPropertyHandler = new FormatPropertyHandler();
public static function NewBooleanProperty(nameValue:String, defaultValue:Boolean, inherited:Boolean, categories:Vector.<String>):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,sharedBooleanHandler,sharedInheritEnumHandler);
return rslt;
}
public static function NewStringProperty(nameValue:String, defaultValue:String, inherited:Boolean, categories:Vector.<String>):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,sharedStringHandler);
return rslt;
}
public static function NewUintProperty(nameValue:String, defaultValue:uint, inherited:Boolean, categories:Vector.<String>):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,sharedUintHandler,sharedInheritEnumHandler);
return rslt;
}
public static function NewEnumStringProperty(nameValue:String, defaultValue:String, inherited:Boolean, categories:Vector.<String>, ... rest):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,new EnumPropertyHandler(rest),sharedInheritEnumHandler);
return rslt;
}
public static function NewIntOrEnumProperty(nameValue:String, defaultValue:Object, inherited:Boolean, categories:Vector.<String>, minValue:int, maxValue:int, ... rest):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,new EnumPropertyHandler(rest),new IntPropertyHandler(minValue,maxValue),sharedInheritEnumHandler);
return rslt;
}
public static function NewUintOrEnumProperty(nameValue:String, defaultValue:Object, inherited:Boolean, categories:Vector.<String>, ... rest):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,new EnumPropertyHandler(rest),sharedUintHandler,sharedInheritEnumHandler);
return rslt;
}
public static function NewNumberProperty(nameValue:String, defaultValue:Number, inherited:Boolean, categories:Vector.<String>, minValue:Number, maxValue:Number):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,new NumberPropertyHandler(minValue,maxValue),sharedInheritEnumHandler);
return rslt;
}
public static function NewNumberOrPercentOrEnumProperty(nameValue:String, defaultValue:Object, inherited:Boolean, categories:Vector.<String>, minValue:Number, maxValue:Number, minPercentValue:String, maxPercentValue:String, ... rest):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,new EnumPropertyHandler(rest),new PercentPropertyHandler(minPercentValue,maxPercentValue),new NumberPropertyHandler(minValue,maxValue),sharedInheritEnumHandler);
return rslt;
}
public static function NewNumberOrPercentProperty(nameValue:String, defaultValue:Object, inherited:Boolean, categories:Vector.<String>, minValue:Number, maxValue:Number, minPercentValue:String, maxPercentValue:String):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,new PercentPropertyHandler(minPercentValue,maxPercentValue),new NumberPropertyHandler(minValue,maxValue),sharedInheritEnumHandler);
return rslt;
}
public static function NewNumberOrEnumProperty(nameValue:String, defaultValue:Object, inherited:Boolean, categories:Vector.<String>, minValue:Number, maxValue:Number, ... rest):Property
{
var rslt:Property = new Property(nameValue, defaultValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,new EnumPropertyHandler(rest),new NumberPropertyHandler(minValue,maxValue),sharedInheritEnumHandler);
return rslt;
}
public static function NewTabStopsProperty(nameValue:String, defaultValue:Array, inherited:Boolean, categories:Vector.<String>):Property
{
return new TabStopsProperty(nameValue,defaultValue,inherited,categories);
}
public static function NewSpacingLimitProperty(nameValue:String, defaultValue:Object, inherited:Boolean, categories:Vector.<String>, minPercentValue:String, maxPercentValue:String):Property
{
var rslt:Property = new Property(nameValue,defaultValue,inherited,categories);
rslt.addHandlers(sharedUndefinedHandler, new SpacingLimitPropertyHandler(minPercentValue, maxPercentValue), sharedInheritEnumHandler);
return rslt;
}
private static const undefinedValue:* = undefined;
public static function NewTextLayoutFormatProperty(nameValue:String, defaultValue:Object, inherited:Boolean, categories:Vector.<String>):Property
{
// passing undefined as a value seems to confuse the compiler
var rslt:Property = new Property(nameValue, undefinedValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,sharedTextLayoutFormatHandler,sharedInheritEnumHandler);
return rslt;
}
public static function NewListMarkerFormatProperty(nameValue:String, defaultValue:Object, inherited:Boolean, categories:Vector.<String>):Property
{
// passing undefined as a value seems to confuse the compiler
var rslt:Property = new Property(nameValue, undefinedValue, inherited, categories);
rslt.addHandlers(sharedUndefinedHandler,sharedListMarkerFormatHandler,sharedInheritEnumHandler);
return rslt;
}
/** not yet enabled. @private */
public static const NO_LIMITS:String ="noLimits";
/** not yet enabled. @private */
public static const LOWER_LIMIT:String ="lowerLimit";
/** not yet enabled. @private */
public static const UPPER_LIMIT:String = "upperLimit";
/** not yet enabled. @private */
public static const ALL_LIMITS:String = "allLimits";
// storing name here is redundant but is more efficient
private var _name:String;
private var _default:*;
private var _inherited:Boolean;
private var _categories:Vector.<String>;
private var _hasCustomExporterHandler:Boolean;
private var _numberPropertyHandler:NumberPropertyHandler;
protected var _handlers:Vector.<PropertyHandler>;
/** Initializer. Each property has a name and a default. */
public function Property(nameValue:String,defaultValue:*,inherited:Boolean,categories:Vector.<String>)
{
_name = nameValue;
_default = defaultValue;
_inherited = inherited;
_categories = categories;
_hasCustomExporterHandler = false;
}
/** The name of the property */
public function get name():String
{ return _name; }
/** The default value of this property */
public function get defaultValue():*
{ return _default; }
/** Is this property inherited */
public function get inherited():Object
{ return _inherited; }
/** First listed Category of this property. This is the legacy category from when Properties could only be in one category. */
public function get category():String
{ return _categories[0]; }
/** Return the list of categories */
public function get categories():Vector.<String>
{ return _categories; }
public function addHandlers(... rest):void
{
_handlers = new Vector.<PropertyHandler>(rest.length,true);
for (var idx:int = 0; idx < rest.length; idx++)
{
var handler:PropertyHandler = rest[idx]
_handlers[idx] = handler;
if (handler.customXMLStringHandler)
_hasCustomExporterHandler = true;
if (handler is NumberPropertyHandler)
_numberPropertyHandler = handler as NumberPropertyHandler;
}
}
public function findHandler(handlerClass:Class):PropertyHandler
{
for each (var prop:PropertyHandler in _handlers)
{
if (prop is handlerClass)
return prop;
}
return null;
}
/** Helper function when setting the property */
public function setHelper(currVal:*,newVal:*):*
{
for each (var handler:PropertyHandler in _handlers)
{
var checkRslt:* = handler.owningHandlerCheck(newVal);
if (checkRslt !== undefined)
return handler.setHelper(checkRslt);
}
Property.errorHandler(this,newVal);
return currVal;
}
/** Helper function when merging the property to compute actual attributes */
public function concatInheritOnlyHelper(currVal:*,concatVal:*):*
{ return (_inherited && currVal === undefined) || currVal == FormatValue.INHERIT ? concatVal : currVal; }
public static function defaultConcatHelper(currVal:*,concatVal:*):*
{ return currVal === undefined || currVal == FormatValue.INHERIT ? concatVal : currVal; }
/** Helper function when merging the property to compute actual attributes */
public function concatHelper(currVal:*,concatVal:*):*
{
if (_inherited)
return currVal === undefined || currVal == FormatValue.INHERIT ? concatVal : currVal;
if (currVal === undefined)
return defaultValue;
return currVal == FormatValue.INHERIT ? concatVal : currVal;
}
/** Helper function when comparing the property */
public function equalHelper(v1:*,v2:*):Boolean
{ return v1 == v2; }
/** Convert the value of this property to a string appropriate for XML export */
public function toXMLString(val:Object):String
{
if (_hasCustomExporterHandler)
{
for each (var prop:PropertyHandler in _handlers)
{
if (prop.customXMLStringHandler && prop.owningHandlerCheck(val) !== undefined)
return prop.toXMLString(val);
}
}
return val.toString();
}
public function get maxPercentValue():Number
{
var handler:PercentPropertyHandler = findHandler(PercentPropertyHandler) as PercentPropertyHandler;
return handler ? handler.maxValue : NaN;
}
public function get minPercentValue():Number
{
var handler:PercentPropertyHandler = findHandler(PercentPropertyHandler) as PercentPropertyHandler;
return handler ? handler.minValue : NaN;
}
public function get minValue():Number
{
var numberHandler:NumberPropertyHandler = findHandler(NumberPropertyHandler) as NumberPropertyHandler;
if (numberHandler)
return numberHandler.minValue;
var intHandler:IntPropertyHandler = findHandler(IntPropertyHandler) as IntPropertyHandler;
return intHandler ? intHandler.minValue : NaN;
}
public function get maxValue():Number
{
var numberHandler:NumberPropertyHandler = findHandler(NumberPropertyHandler) as NumberPropertyHandler;
if (numberHandler)
return numberHandler.maxValue;
var intHandler:IntPropertyHandler = findHandler(IntPropertyHandler) as IntPropertyHandler;
return intHandler ? intHandler.maxValue : NaN;
}
public function computeActualPropertyValue(propertyValue:Object,percentInput:Number):Number
{
var percent:Number = toNumberIfPercent(propertyValue);
if (isNaN(percent))
return Number(propertyValue);
// its a percent - calculate and clamp
var rslt:Number = percentInput * (percent / 100);
return _numberPropertyHandler ? _numberPropertyHandler.clampToRange(rslt) : rslt;
}
// /////////////////////////////////////////////
// Following static functions are used by Format classes to
// perform functions that iterate over all the attributes.
// They are driven by the attributes metadata object that contains
// definitions for all the properties.
// /////////////////////////////////////////////
/** Helper function to initialize all property values from defaults. */
static public function defaultsAllHelper(description:Object,current:Object):void
{
for each (var prop:Property in description)
current[prop.name] = prop.defaultValue;
}
/** Helper function to compare two sets of properties. */
static public function equalAllHelper(description:Object,p1:Object,p2:Object):Boolean
{
if (p1 == p2)
return true;
// these could be "equal" if all attributes of p1 or p2 are null
if (p1 == null || p2 == null)
return false;
for each (var prop:Property in description)
{
var name:String = prop.name;
if (!(prop.equalHelper(p1[name],p2[name])))
return false;
}
return true;
}
static public function extractInCategory(formatClass:Class,description:Object,props:Object,category:String,legacy:Boolean = true):Object
{
var rslt:Object = null;
for each (var prop:Property in description)
{
if (props[prop.name] == null)
continue;
if (legacy)
{
if (prop.category != category)
continue;
}
else if (prop.categories.indexOf(category) == -1)
continue;
if (rslt == null)
rslt = new formatClass();
rslt[prop.name] = props[prop.name];
}
return rslt;
}
/** @private Copy an object */
static public function shallowCopy(src:Object):Object
{
// make a shallow copy
var rslt:Object = new Object()
for (var val:Object in src)
rslt[val] = src[val];
return rslt;
}
/** @private Copy properties from src to result if a property of the same name exists in filter */
static public function shallowCopyInFilter(src:Object,filter:Object):Object
{
// make a shallow copy
var rslt:Object = new Object()
for (var val:Object in src)
{
if (filter.hasOwnProperty(val))
rslt[val] = src[val];
}
return rslt;
}
/** @private Copy properties from src to result if a property of the same name exists in filter */
static public function shallowCopyNotInFilter(src:Object,filter:Object):Object
{
// make a shallow copy
var rslt:Object = new Object()
for (var val:Object in src)
{
if (!filter.hasOwnProperty(val))
rslt[val] = src[val];
}
return rslt;
}
static private function compareStylesLoop(o1:Object,o2:Object,description:Object):Boolean
{
for (var val:String in o1)
{
var o1val:Object = o1[val];
var o2val:Object = o2[val];
if (o1val != o2val)
{
if (!(o1val is Array) || !(o2val is Array) || o1val.length != o2val.length || !description)
return false; // different
var prop:ArrayProperty = description[val];
if (!prop || !equalAllHelper(prop.memberType.tlf_internal::description,o1val,o2val))
return false;
}
}
return true;
}
/** @private */
static tlf_internal const nullStyleObject:Object = new Object();
/** @private */
static public function equalStyles(o1:Object,o2:Object,description:Object):Boolean
{
if (o1 == null)
o1 = nullStyleObject;
if (o2 == null)
o2 = nullStyleObject;
// Use of prototype chains and bug https://bugzilla.mozilla.org/show_bug.cgi?id=447673 requires this two way compare
return compareStylesLoop(o1,o2,description) && compareStylesLoop(o2,o1,description);
}
/** @private */
static public function toNumberIfPercent(o:Object):Number
{
if (!(o is String))
return NaN;
var s:String = String(o);
var len:int = s.length;
return len != 0 && s.charAt(len-1) == "%" ? parseFloat(s) : NaN;
}
static private var prototypeFactory:Function = function():void
{ }
/** @private Create an object with specified prototype parent */
static public function createObjectWithPrototype(parent:Object):Object
{
prototypeFactory.prototype = parent;
return new prototypeFactory();
}
}
}