/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkAnimatorScript2.h" #include "SkAnimateBase.h" #include "SkAnimateMaker.h" #include "SkDisplayTypes.h" #include "SkExtras.h" #include "SkMemberInfo.h" #include "SkOpArray.h" #include "SkParse.h" #include "SkScript2.h" #include "SkScriptCallBack.h" static const SkDisplayEnumMap gEnumMaps[] = { { SkType_AddMode, "indirect|immediate" }, { SkType_Align, "left|center|right" }, { SkType_ApplyMode, "immediate|once" }, { SkType_ApplyTransition, "reverse" }, { SkType_BitmapEncoding, "jpeg|png" }, { SkType_BitmapFormat, "none|A1|A8|Index8|RGB16|RGB32" }, { SkType_Boolean, "false|true" }, { SkType_Cap, "butt|round|square" }, { SkType_EventCode, "none|up|down|left|right|back|end|OK|send|leftSoftKey|rightSoftKey|key0|key1|key2|key3|key4|key5|key6|key7|key8|key9|star|hash" }, { SkType_EventKind, "none|keyChar|keyPress|mouseDown|mouseDrag|mouseMove|mouseUp|onEnd|onLoad|user" }, { SkType_EventMode, "deferred|immediate" }, { SkType_FillType, "winding|evenOdd" }, { SkType_FilterType, "none|bilinear" }, { SkType_FromPathMode, "normal|angle|position" }, { SkType_Join, "miter|round|blunt" }, { SkType_MaskFilterBlurStyle, "normal|solid|outer|inner" }, { SkType_PathDirection, "cw|ccw" }, { SkType_Style, "fill|stroke|strokeAndFill" }, { SkType_TextBoxAlign, "start|center|end" }, { SkType_TextBoxMode, "oneLine|lineBreak" }, { SkType_TileMode, "clamp|repeat|mirror" }, { SkType_Xfermode, "clear|src|dst|srcOver|dstOver|srcIn|dstIn|srcOut|dstOut|" "srcATop|dstATop|xor|darken|lighten" }, }; static int gEnumMapCount = SK_ARRAY_COUNT(gEnumMaps); class SkAnimatorScript_Box : public SkScriptCallBackConvert { public: SkAnimatorScript_Box() {} ~SkAnimatorScript_Box() { for (SkDisplayable** dispPtr = fTrackDisplayable.begin(); dispPtr < fTrackDisplayable.end(); dispPtr++) delete *dispPtr; } virtual bool convert(SkOperand2::OpType type, SkOperand2* operand) { SkDisplayable* displayable; switch (type) { case SkOperand2::kArray: { SkDisplayArray* boxedValue = new SkDisplayArray(*operand->fArray); displayable = boxedValue; } break; case SkOperand2::kS32: { SkDisplayInt* boxedValue = new SkDisplayInt; displayable = boxedValue; boxedValue->value = operand->fS32; } break; case SkOperand2::kScalar: { SkDisplayFloat* boxedValue = new SkDisplayFloat; displayable = boxedValue; boxedValue->value = operand->fScalar; } break; case SkOperand2::kString: { SkDisplayString* boxedValue = new SkDisplayString(*operand->fString); displayable = boxedValue; } break; case SkOperand2::kObject: return true; default: SkASSERT(0); return false; } track(displayable); operand->fObject = (void*) displayable; return true; } virtual SkOperand2::OpType getReturnType(int index) { return SkOperand2::kObject; } virtual Type getType() const { return kBox; } void track(SkDisplayable* displayable) { SkASSERT(fTrackDisplayable.find(displayable) < 0); *fTrackDisplayable.append() = displayable; } SkTDDisplayableArray fTrackDisplayable; }; class SkAnimatorScript_Enum : public SkScriptCallBackProperty { public: SkAnimatorScript_Enum(const char* tokens) : fTokens(tokens) {} virtual bool getConstValue(const char* name, int len, SkOperand2* value) { return SkAnimatorScript2::MapEnums(fTokens, name, len, &value->fS32); } private: const char* fTokens; }; // !!! if type is string, call invoke // if any other type, return original value // distinction is undone: could do this by returning index == 0 only if param is string // still, caller of getParamTypes will attempt to convert param to string (I guess) class SkAnimatorScript_Eval : public SkScriptCallBackFunction { public: SkAnimatorScript_Eval(SkAnimatorScript2* engine) : fEngine(engine) {} virtual bool getIndex(const char* name, int len, size_t* result) { if (SK_LITERAL_STR_EQUAL("eval", name, len) != 0) return false; *result = 0; return true; } virtual void getParamTypes(SkIntArray(SkOperand2::OpType)* types) { types->setCount(1); SkOperand2::OpType* type = types->begin(); type[0] = SkOperand2::kString; } virtual bool invoke(size_t index, SkOpArray* params, SkOperand2* answer) { SkAnimatorScript2 engine(fEngine->getMaker(), fEngine->getWorking(), SkAnimatorScript2::ToDisplayType(fEngine->getReturnType())); SkOperand2* op = params->begin(); const char* script = op->fString->c_str(); SkScriptValue2 value; return engine.evaluateScript(&script, &value); SkASSERT(value.fType == fEngine->getReturnType()); *answer = value.fOperand; // !!! incomplete ? return true; } private: SkAnimatorScript2* fEngine; }; class SkAnimatorScript_ID : public SkScriptCallBackProperty { public: SkAnimatorScript_ID(SkAnimatorScript2* engine) : fEngine(engine) {} virtual bool getIndex(const char* token, int len, size_t* result) { SkDisplayable* displayable; bool success = fEngine->getMaker().find(token, len, &displayable); if (success == false) { *result = 0; } else { *result = (size_t) displayable; SkDisplayable* working = fEngine->getWorking(); if (displayable->canContainDependents() && working && working->isAnimate()) { SkAnimateBase* animator = (SkAnimateBase*) working; if (animator->isDynamic()) { SkDisplayDepend* depend = (SkDisplayDepend* ) displayable; depend->addDependent(working); } } } return true; } virtual bool getResult(size_t ref, SkOperand2* answer) { answer->fObject = (void*) ref; return true; } virtual SkOperand2::OpType getReturnType(size_t index) { return index == 0 ? SkOperand2::kString : SkOperand2::kObject; } private: SkAnimatorScript2* fEngine; }; class SkAnimatorScript_Member : public SkScriptCallBackMember { public: SkAnimatorScript_Member(SkAnimatorScript2* engine) : fEngine(engine) {} bool getMemberReference(const char* member, size_t len, void* object, SkScriptValue2* ref) { SkDisplayable* displayable = (SkDisplayable*) object; SkString name(member, len); SkDisplayable* named = displayable->contains(name); if (named) { ref->fType = SkOperand2::kObject; ref->fOperand.fObject = named; return true; } const SkMemberInfo* info = displayable->getMember(name.c_str()); if (info == NULL) return false; // !!! add additional error info? ref->fType = SkAnimatorScript2::ToOpType(info->getType()); ref->fOperand.fObject = (void*) info; return true; } bool invoke(size_t ref, void* object, SkOperand2* value) { const SkMemberInfo* info = (const SkMemberInfo* ) ref; SkDisplayable* displayable = (SkDisplayable*) object; if (info->fType == SkType_MemberProperty) { if (displayable->getProperty2(info->propertyIndex(), value) == false) { return false; } } return fEngine->evalMemberCommon(info, displayable, value); } SkAnimatorScript2* fEngine; }; class SkAnimatorScript_MemberFunction : public SkScriptCallBackMemberFunction { public: SkAnimatorScript_MemberFunction(SkAnimatorScript2* engine) : fEngine(engine) {} bool getMemberReference(const char* member, size_t len, void* object, SkScriptValue2* ref) { SkDisplayable* displayable = (SkDisplayable*) object; SkString name(member, len); const SkMemberInfo* info = displayable->getMember(name.c_str()); if (info == NULL || info->fType != SkType_MemberFunction) return false; // !!! add additional error info? ref->fType = SkAnimatorScript2::ToOpType(info->getType()); ref->fOperand.fObject = (void*) info; return true; } virtual void getParamTypes(SkIntArray(SkOperand2::OpType)* types) { types->setCount(3); SkOperand2::OpType* type = types->begin(); type[0] = type[1] = type[2] = SkOperand2::kS32; } bool invoke(size_t ref, void* object, SkOpArray* params, SkOperand2* value) { const SkMemberInfo* info = (const SkMemberInfo* ) ref; SkDisplayable* displayable = (SkDisplayable*) object; displayable->executeFunction2(displayable, info->functionIndex(), params, info->getType(), value); return fEngine->evalMemberCommon(info, displayable, value); } SkAnimatorScript2* fEngine; }; class SkAnimatorScript_NamedColor : public SkScriptCallBackProperty { public: virtual bool getConstValue(const char* name, int len, SkOperand2* value) { return SkParse::FindNamedColor(name, len, (SkColor*) &value->fS32) != NULL; } }; class SkAnimatorScript_RGB : public SkScriptCallBackFunction { public: virtual bool getIndex(const char* name, int len, size_t* result) { if (SK_LITERAL_STR_EQUAL("rgb", name, len) != 0) return false; *result = 0; return true; } virtual void getParamTypes(SkIntArray(SkOperand2::OpType)* types) { types->setCount(3); SkOperand2::OpType* type = types->begin(); type[0] = type[1] = type[2] = SkOperand2::kS32; } virtual bool invoke(size_t index, SkOpArray* params, SkOperand2* answer) { SkASSERT(index == 0); unsigned result = 0xFF000000; int shift = 16; for (int index = 0; index < 3; index++) { result |= SkClampMax(params->begin()[index].fS32, 255) << shift; shift -= 8; } answer->fS32 = result; return true; } }; class SkAnimatorScript_Unbox : public SkScriptCallBackConvert { public: SkAnimatorScript_Unbox(SkAnimatorScript2* engine) : fEngine(engine) {} virtual bool convert(SkOperand2::OpType type, SkOperand2* operand) { SkASSERT(type == SkOperand2::kObject); SkDisplayable* displayable = (SkDisplayable*) operand->fObject; switch (displayable->getType()) { case SkType_Array: { SkDisplayArray* boxedValue = (SkDisplayArray*) displayable; operand->fArray = new SkOpArray(SkAnimatorScript2::ToOpType(boxedValue->values.getType())); int count = boxedValue->values.count(); operand->fArray->setCount(count); memcpy(operand->fArray->begin(), boxedValue->values.begin(), count * sizeof(SkOperand2)); fEngine->track(operand->fArray); } break; case SkType_Boolean: { SkDisplayBoolean* boxedValue = (SkDisplayBoolean*) displayable; operand->fS32 = boxedValue->value; } break; case SkType_Int: { SkDisplayInt* boxedValue = (SkDisplayInt*) displayable; operand->fS32 = boxedValue->value; } break; case SkType_Float: { SkDisplayFloat* boxedValue = (SkDisplayFloat*) displayable; operand->fScalar = boxedValue->value; } break; case SkType_String: { SkDisplayString* boxedValue = (SkDisplayString*) displayable; operand->fString = SkNEW_ARGS(SkString, (boxedValue->value)); } break; default: { const char* id; bool success = fEngine->getMaker().findKey(displayable, &id); SkASSERT(success); operand->fString = SkNEW_ARGS(SkString, (id)); } } return true; } virtual SkOperand2::OpType getReturnType(int /*index*/, SkOperand2* operand) { SkDisplayable* displayable = (SkDisplayable*) operand->fObject; switch (displayable->getType()) { case SkType_Array: return SkOperand2::kArray; case SkType_Int: return SkOperand2::kS32; case SkType_Float: return SkOperand2::kScalar; case SkType_String: default: return SkOperand2::kString; } } virtual Type getType() const { return kUnbox; } SkAnimatorScript2* fEngine; }; SkAnimatorScript2::SkAnimatorScript2(SkAnimateMaker& maker, SkDisplayable* working, SkDisplayTypes type) : SkScriptEngine2(ToOpType(type)), fMaker(maker), fWorking(working) { *fCallBackArray.append() = new SkAnimatorScript_Member(this); *fCallBackArray.append() = new SkAnimatorScript_MemberFunction(this); *fCallBackArray.append() = new SkAnimatorScript_Box(); *fCallBackArray.append() = new SkAnimatorScript_Unbox(this); *fCallBackArray.append() = new SkAnimatorScript_ID(this); if (type == SkType_ARGB) { *fCallBackArray.append() = new SkAnimatorScript_RGB(); *fCallBackArray.append() = new SkAnimatorScript_NamedColor(); } if (SkDisplayType::IsEnum(&maker, type)) { // !!! for SpiderMonkey, iterate through the enum values, and map them to globals const SkDisplayEnumMap& map = GetEnumValues(type); *fCallBackArray.append() = new SkAnimatorScript_Enum(map.fValues); } *fCallBackArray.append() = new SkAnimatorScript_Eval(this); #if 0 // !!! no extra support for now for (SkExtras** extraPtr = maker.fExtras.begin(); extraPtr < maker.fExtras.end(); extraPtr++) { SkExtras* extra = *extraPtr; if (extra->fExtraCallBack) *fCallBackArray.append() = new propertyCallBack(extra->fExtraCallBack, extra->fExtraStorage); } #endif } SkAnimatorScript2::~SkAnimatorScript2() { SkScriptCallBack** end = fCallBackArray.end(); for (SkScriptCallBack** ptr = fCallBackArray.begin(); ptr < end; ptr++) delete *ptr; } bool SkAnimatorScript2::evalMemberCommon(const SkMemberInfo* info, SkDisplayable* displayable, SkOperand2* value) { SkDisplayTypes original; SkDisplayTypes type = original = (SkDisplayTypes) info->getType(); if (info->fType == SkType_Array) type = SkType_Array; switch (type) { case SkType_ARGB: type = SkType_Int; case SkType_Boolean: case SkType_Int: case SkType_MSec: case SkType_Float: SkASSERT(info->getCount() == 1); if (info->fType != SkType_MemberProperty && info->fType != SkType_MemberFunction) value->fS32 = *(int32_t*) info->memberData(displayable); // OK for SkScalar too if (type == SkType_MSec) { value->fScalar = SkScalarDiv((SkScalar) value->fS32, 1000); // dividing two ints is the same as dividing two scalars type = SkType_Float; } break; case SkType_String: { SkString* displayableString; if (info->fType != SkType_MemberProperty && info->fType != SkType_MemberFunction) { info->getString(displayable, &displayableString); value->fString = new SkString(*displayableString); } } break; case SkType_Array: { SkASSERT(info->fType != SkType_MemberProperty); // !!! incomplete SkTDOperandArray* displayableArray = (SkTDOperandArray*) info->memberData(displayable); if (displayable->getType() == SkType_Array) { SkDisplayArray* typedArray = (SkDisplayArray*) displayable; original = typedArray->values.getType(); } SkASSERT(original != SkType_Unknown); SkOpArray* array = value->fArray = new SkOpArray(ToOpType(original)); track(array); int count = displayableArray->count(); if (count > 0) { array->setCount(count); memcpy(array->begin(), displayableArray->begin(), count * sizeof(SkOperand2)); } } break; default: SkASSERT(0); // unimplemented } return true; } const SkDisplayEnumMap& SkAnimatorScript2::GetEnumValues(SkDisplayTypes type) { int index = SkTSearch<SkDisplayTypes>(&gEnumMaps[0].fType, gEnumMapCount, type, sizeof(SkDisplayEnumMap)); SkASSERT(index >= 0); return gEnumMaps[index]; } SkDisplayTypes SkAnimatorScript2::ToDisplayType(SkOperand2::OpType type) { int val = type; switch (val) { case SkOperand2::kNoType: return SkType_Unknown; case SkOperand2::kS32: return SkType_Int; case SkOperand2::kScalar: return SkType_Float; case SkOperand2::kString: return SkType_String; case SkOperand2::kArray: return SkType_Array; case SkOperand2::kObject: return SkType_Displayable; default: SkASSERT(0); return SkType_Unknown; } } SkOperand2::OpType SkAnimatorScript2::ToOpType(SkDisplayTypes type) { if (SkDisplayType::IsDisplayable(NULL /* fMaker */, type)) return SkOperand2::kObject; if (SkDisplayType::IsEnum(NULL /* fMaker */, type)) return SkOperand2::kS32; switch (type) { case SkType_ARGB: case SkType_MSec: case SkType_Int: return SkOperand2::kS32; case SkType_Float: case SkType_Point: case SkType_3D_Point: return SkOperand2::kScalar; case SkType_Base64: case SkType_DynamicString: case SkType_String: return SkOperand2::kString; case SkType_Array: return SkOperand2::kArray; case SkType_Unknown: return SkOperand2::kNoType; default: SkASSERT(0); return SkOperand2::kNoType; } } bool SkAnimatorScript2::MapEnums(const char* ptr, const char* match, size_t len, int* value) { int index = 0; bool more = true; do { const char* last = strchr(ptr, '|'); if (last == NULL) { last = &ptr[strlen(ptr)]; more = false; } size_t length = last - ptr; if (len == length && strncmp(ptr, match, length) == 0) { *value = index; return true; } index++; ptr = last + 1; } while (more); return false; } #if defined SK_DEBUG #include "SkAnimator.h" static const char scriptTestSetup[] = "<screenplay>" "<apply>" "<paint>" "<emboss id='emboss' direction='[1,1,1]' />" "</paint>" "<animateField id='animation' field='direction' target='emboss' from='[1,1,1]' to='[-1,1,1]' dur='1'/>" "<set lval='direction[0]' target='emboss' to='-1' />" "</apply>" "<color id='testColor' color='0 ? rgb(0,0,0) : rgb(255,255,255)' />" "<color id='xColor' color='rgb(12,34,56)' />" "<typedArray id='emptyArray' />" "<typedArray id='intArray' values='[1, 4, 6]' />" "<s32 id='idx' value='2' />" "<s32 id='idy' value='2' />" "<string id='alpha' value='abc' />" "<rectangle id='testRect' left='Math.cos(0)' top='2' right='12' bottom='5' />" "<event id='evt'>" "<input name='x' />" "<apply scope='idy'>" "<set field='value' to='evt.x.s32' />" "</apply>" "</event>" "</screenplay>"; static const SkScriptNAnswer scriptTests[] = { { "alpha+alpha", SkType_String, 0, 0, "abcabc" }, { "0 ? Math.sin(0) : 1", SkType_Int, 1 }, { "intArray[4]", SkType_Unknown }, { "emptyArray[4]", SkType_Unknown }, { "idx", SkType_Int, 2 }, { "intArray.length", SkType_Int, 3 }, { "intArray.values[0]", SkType_Int, 1 }, { "intArray[0]", SkType_Int, 1 }, { "idx.value", SkType_Int, 2 }, { "alpha.value", SkType_String, 0, 0, "abc" }, { "alpha", SkType_String, 0, 0, "abc" }, { "alpha.value+alpha.value", SkType_String, 0, 0, "abcabc" }, { "alpha+idx", SkType_String, 0, 0, "abc2" }, { "idx+alpha", SkType_String, 0, 0, "2abc" }, { "intArray[idx]", SkType_Int, 6 }, { "alpha.slice(1,2)", SkType_String, 0, 0, "b" }, { "alpha.value.slice(1,2)", SkType_String, 0, 0, "b" }, { "Math.sin(0)", SkType_Float, 0, SkIntToScalar(0) }, { "testRect.left+2", SkType_Float, 0, SkIntToScalar(3) }, { "0 ? intArray[0] : 1", SkType_Int, 1 }, { "0 ? intArray.values[0] : 1", SkType_Int, 1 }, { "0 ? idx : 1", SkType_Int, 1 }, { "0 ? idx.value : 1", SkType_Int, 1 }, { "0 ? alpha.slice(1,2) : 1", SkType_Int, 1 }, { "0 ? alpha.value.slice(1,2) : 1", SkType_Int, 1 }, { "idy", SkType_Int, 3 } }; #define SkScriptNAnswer_testCount SK_ARRAY_COUNT(scriptTests) void SkAnimatorScript2::UnitTest() { #if defined(SK_SUPPORT_UNITTEST) SkAnimator animator; SkASSERT(animator.decodeMemory(scriptTestSetup, sizeof(scriptTestSetup)-1)); SkEvent evt; evt.setString("id", "evt"); evt.setS32("x", 3); animator.doUserEvent(evt); // set up animator with memory script above, then run value tests for (int index = 0; index < SkScriptNAnswer_testCount; index++) { SkAnimatorScript2 engine(*animator.fMaker, NULL, scriptTests[index].fType); SkScriptValue2 value; const char* script = scriptTests[index].fScript; bool success = engine.evaluateScript(&script, &value); if (success == false) { SkASSERT(scriptTests[index].fType == SkType_Unknown); continue; } SkASSERT(value.fType == ToOpType(scriptTests[index].fType)); SkScalar error; switch (value.fType) { case SkOperand2::kS32: SkASSERT(value.fOperand.fS32 == scriptTests[index].fIntAnswer); break; case SkOperand2::kScalar: error = SkScalarAbs(value.fOperand.fScalar - scriptTests[index].fScalarAnswer); SkASSERT(error < SK_Scalar1 / 10000); break; case SkOperand2::kString: SkASSERT(value.fOperand.fString->equals(scriptTests[index].fStringAnswer)); break; default: SkASSERT(0); } } #endif } #endif