diff --git a/docs/xml/modules/classes/vector.xml b/docs/xml/modules/classes/vector.xml index e752cdc08..513d9d7a6 100644 --- a/docs/xml/modules/classes/vector.xml +++ b/docs/xml/modules/classes/vector.xml @@ -120,9 +120,10 @@ NewMatrix Returns a VectorMatrix structure that allows transformations to be applied to the vector. - ERR vecNewMatrix(OBJECTPTR Object, struct VectorMatrix ** Transform) + ERR vecNewMatrix(OBJECTPTR Object, struct VectorMatrix ** Transform, LONG End) A reference to the new transform structure is returned here. + If true, the matrix priority is lowered by inserting it at the end of the transform list.

Call NewMatrix() to allocate a transformation matrix that allows transforms to be applied to a vector. Manipulating the transformation matrix is supported by functions in the Vector module, such as Scale and Rotate.

@@ -252,7 +253,7 @@ Set to TRUE if all transforms applicable to the vector should be applied to the path. -

Any vector that generates a path can be traced by calling this method. Tracing allows the caller to follow the path from point-to-point if the path were to be rendered with a stroke. The prototype of the callback function is ERR Function(OBJECTPTR Vector, LONG Index, LONG Command, DOUBLE X, DOUBLE Y, APTR Meta).

+

Any vector that generates a path can be traced by calling this method. Tracing allows the caller to follow the path from point-to-point if the path were to be rendered with a stroke. The prototype of the callback function is ERR Function(OBJECTPTR Vector, LONG Index, LONG Command, DOUBLE X, DOUBLE Y, APTR Meta).

The Vector parameter refers to the vector targeted by the method. The Index is an incrementing counter that reflects the currently plotted point. The X and Y parameters reflect the coordinate of a point on the path.

If the Callback returns ERR::Terminate, then no further coordinates will be processed.

@@ -800,6 +801,7 @@ Z: Close Path Matrix value D Matrix value E Matrix value F + An optional tag value defined by the client for matrix identification. diff --git a/docs/xml/modules/classes/vectorgradient.xml b/docs/xml/modules/classes/vectorgradient.xml index d453092b5..d80f9102c 100644 --- a/docs/xml/modules/classes/vectorgradient.xml +++ b/docs/xml/modules/classes/vectorgradient.xml @@ -311,6 +311,7 @@ Matrix value D Matrix value E Matrix value F + An optional tag value defined by the client for matrix identification. diff --git a/docs/xml/modules/classes/vectorpattern.xml b/docs/xml/modules/classes/vectorpattern.xml index b6a72796f..43f3d65b0 100644 --- a/docs/xml/modules/classes/vectorpattern.xml +++ b/docs/xml/modules/classes/vectorpattern.xml @@ -186,6 +186,7 @@ Matrix value D Matrix value E Matrix value F + An optional tag value defined by the client for matrix identification. diff --git a/docs/xml/modules/classes/vectorviewport.xml b/docs/xml/modules/classes/vectorviewport.xml index 219b9328c..3b8d0642d 100644 --- a/docs/xml/modules/classes/vectorviewport.xml +++ b/docs/xml/modules/classes/vectorviewport.xml @@ -282,6 +282,7 @@ Matrix value D Matrix value E Matrix value F + An optional tag value defined by the client for matrix identification. diff --git a/docs/xml/modules/vector.xml b/docs/xml/modules/vector.xml index 5fe45abab..e714ea7e3 100644 --- a/docs/xml/modules/vector.xml +++ b/docs/xml/modules/vector.xml @@ -987,6 +987,7 @@ Z: Close Path Matrix value D Matrix value E Matrix value F + An optional tag value defined by the client for matrix identification. diff --git a/include/parasol/main.h b/include/parasol/main.h index fc6db55ba..6eb18f71e 100644 --- a/include/parasol/main.h +++ b/include/parasol/main.h @@ -37,13 +37,19 @@ namespace pf { template struct POINT { T x, y; + + constexpr POINT & operator += (const POINT &Other) { + x += Other.x; + y += Other.y; + return *this; + } }; template bool operator==(const POINT &a, const POINT &b) { return (a.x == b.x) and (a.y == b.y); } -template T operator-(POINT A, const POINT &B) { +template T operator-(const POINT A, const POINT &B) { if (A == B) return 0; double a = std::abs(B.x - A.x); double b = std::abs(B.y - A.y); @@ -52,6 +58,10 @@ template T operator-(POINT A, const POINT &B) { //return T(std::sqrt((a * a) + (b * b))); // Full accuracy } +template POINT operator * (const POINT &lhs, const double &Multiplier) { + return POINT { lhs.x * Multiplier, lhs.y * Multiplier }; +} + //******************************************************************************************************************** DEFINE_ENUM_FLAG_OPERATORS(ERR) @@ -429,7 +439,7 @@ inline FieldValue FileDescription(const std::string &Value) { return FieldValue( constexpr FieldValue FileHeader(CSTRING Value) { return FieldValue(FID_FileHeader, Value); } inline FieldValue FileHeader(const std::string &Value) { return FieldValue(FID_FileHeader, Value.c_str()); } -constexpr FieldValue FontSize(DOUBLE Value) { return FieldValue(FID_FontSize, Value); } +constexpr FieldValue FontSize(double Value) { return FieldValue(FID_FontSize, Value); } constexpr FieldValue FontSize(LONG Value) { return FieldValue(FID_FontSize, Value); } constexpr FieldValue FontSize(CSTRING Value) { return FieldValue(FID_FontSize, Value); } inline FieldValue FontSize(const std::string &Value) { return FieldValue(FID_FontSize, Value.c_str()); } @@ -452,7 +462,7 @@ constexpr FieldValue ReadOnly(bool Value) { return FieldValue(FID_ReadOnly, (Val constexpr FieldValue ButtonOrder(CSTRING Value) { return FieldValue(FID_ButtonOrder, Value); } inline FieldValue ButtonOrder(const std::string &Value) { return FieldValue(FID_ButtonOrder, Value.c_str()); } -constexpr FieldValue Point(DOUBLE Value) { return FieldValue(FID_Point, Value); } +constexpr FieldValue Point(double Value) { return FieldValue(FID_Point, Value); } constexpr FieldValue Point(LONG Value) { return FieldValue(FID_Point, Value); } constexpr FieldValue Point(CSTRING Value) { return FieldValue(FID_Point, Value); } inline FieldValue Point(const std::string &Value) { return FieldValue(FID_Point, Value.c_str()); } @@ -463,7 +473,7 @@ inline FieldValue Points(const std::string &Value) { return FieldValue(FID_Point constexpr FieldValue Pretext(CSTRING Value) { return FieldValue(FID_Pretext, Value); } inline FieldValue Pretext(const std::string &Value) { return FieldValue(FID_Pretext, Value.c_str()); } -constexpr FieldValue Acceleration(DOUBLE Value) { return FieldValue(FID_Acceleration, Value); } +constexpr FieldValue Acceleration(double Value) { return FieldValue(FID_Acceleration, Value); } constexpr FieldValue Actions(CPTR Value) { return FieldValue(FID_Actions, Value); } constexpr FieldValue AmtColours(LONG Value) { return FieldValue(FID_AmtColours, Value); } constexpr FieldValue BaseClassID(LONG Value) { return FieldValue(FID_BaseClassID, Value); } @@ -472,11 +482,11 @@ constexpr FieldValue BitsPerPixel(LONG Value) { return FieldValue(FID_BitsPerPix constexpr FieldValue BytesPerPixel(LONG Value) { return FieldValue(FID_BytesPerPixel, Value); } constexpr FieldValue Category(CCF Value) { return FieldValue(FID_Category, LONG(Value)); } constexpr FieldValue ClassID(LONG Value) { return FieldValue(FID_ClassID, Value); } -constexpr FieldValue ClassVersion(DOUBLE Value) { return FieldValue(FID_ClassVersion, Value); } +constexpr FieldValue ClassVersion(double Value) { return FieldValue(FID_ClassVersion, Value); } constexpr FieldValue Closed(bool Value) { return FieldValue(FID_Closed, (Value ? 1 : 0)); } constexpr FieldValue Cursor(PTC Value) { return FieldValue(FID_Cursor, LONG(Value)); } constexpr FieldValue DataFlags(MEM Value) { return FieldValue(FID_DataFlags, LONG(Value)); } -constexpr FieldValue DoubleClick(DOUBLE Value) { return FieldValue(FID_DoubleClick, Value); } +constexpr FieldValue DoubleClick(double Value) { return FieldValue(FID_DoubleClick, Value); } constexpr FieldValue Feedback(CPTR Value) { return FieldValue(FID_Feedback, Value); } constexpr FieldValue Fields(const FieldArray *Value) { return FieldValue(FID_Fields, Value, FD_ARRAY); } constexpr FieldValue Flags(LONG Value) { return FieldValue(FID_Flags, Value); } @@ -489,28 +499,28 @@ constexpr FieldValue Listener(LONG Value) { return FieldValue(FID_Listener, Valu constexpr FieldValue MatrixColumns(LONG Value) { return FieldValue(FID_MatrixColumns, Value); } constexpr FieldValue MatrixRows(LONG Value) { return FieldValue(FID_MatrixRows, Value); } constexpr FieldValue MaxHeight(LONG Value) { return FieldValue(FID_MaxHeight, Value); } -constexpr FieldValue MaxSpeed(DOUBLE Value) { return FieldValue(FID_MaxSpeed, Value); } +constexpr FieldValue MaxSpeed(double Value) { return FieldValue(FID_MaxSpeed, Value); } constexpr FieldValue MaxWidth(LONG Value) { return FieldValue(FID_MaxWidth, Value); } constexpr FieldValue Methods(const MethodEntry *Value) { return FieldValue(FID_Methods, Value, FD_ARRAY); } -constexpr FieldValue Opacity(DOUBLE Value) { return FieldValue(FID_Opacity, Value); } +constexpr FieldValue Opacity(double Value) { return FieldValue(FID_Opacity, Value); } constexpr FieldValue Owner(OBJECTID Value) { return FieldValue(FID_Owner, Value); } constexpr FieldValue Parent(OBJECTID Value) { return FieldValue(FID_Parent, Value); } constexpr FieldValue Permissions(PERMIT Value) { return FieldValue(FID_Permissions, LONG(Value)); } constexpr FieldValue Picture(OBJECTPTR Value) { return FieldValue(FID_Picture, Value); } constexpr FieldValue PopOver(OBJECTID Value) { return FieldValue(FID_PopOver, Value); } -constexpr FieldValue RefreshRate(DOUBLE Value) { return FieldValue(FID_RefreshRate, Value); } +constexpr FieldValue RefreshRate(double Value) { return FieldValue(FID_RefreshRate, Value); } constexpr FieldValue Routine(CPTR Value) { return FieldValue(FID_Routine, Value); } constexpr FieldValue Size(LONG Value) { return FieldValue(FID_Size, Value); } -constexpr FieldValue Speed(DOUBLE Value) { return FieldValue(FID_Speed, Value); } -constexpr FieldValue StrokeWidth(DOUBLE Value) { return FieldValue(FID_StrokeWidth, Value); } +constexpr FieldValue Speed(double Value) { return FieldValue(FID_Speed, Value); } +constexpr FieldValue StrokeWidth(double Value) { return FieldValue(FID_StrokeWidth, Value); } constexpr FieldValue Surface(OBJECTID Value) { return FieldValue(FID_Surface, Value); } constexpr FieldValue Target(OBJECTID Value) { return FieldValue(FID_Target, Value); } constexpr FieldValue Target(OBJECTPTR Value) { return FieldValue(FID_Target, Value); } constexpr FieldValue UserData(CPTR Value) { return FieldValue(FID_UserData, Value); } -constexpr FieldValue Version(DOUBLE Value) { return FieldValue(FID_Version, Value); } +constexpr FieldValue Version(double Value) { return FieldValue(FID_Version, Value); } constexpr FieldValue Viewport(OBJECTID Value) { return FieldValue(FID_Viewport, Value); } constexpr FieldValue Viewport(OBJECTPTR Value) { return FieldValue(FID_Viewport, Value); } -constexpr FieldValue WheelSpeed(DOUBLE Value) { return FieldValue(FID_WheelSpeed, Value); } +constexpr FieldValue WheelSpeed(double Value) { return FieldValue(FID_WheelSpeed, Value); } constexpr FieldValue WindowHandle(APTR Value) { return FieldValue(FID_WindowHandle, Value); } constexpr FieldValue WindowHandle(LONG Value) { return FieldValue(FID_WindowHandle, Value); } diff --git a/include/parasol/modules/vector.h b/include/parasol/modules/vector.h index ba1765c65..6077b2f5e 100644 --- a/include/parasol/modules/vector.h +++ b/include/parasol/modules/vector.h @@ -558,8 +558,15 @@ struct VectorMatrix { DOUBLE ScaleY; // Matrix value D DOUBLE TranslateX; // Matrix value E DOUBLE TranslateY; // Matrix value F + LONG Tag; // An optional tag value defined by the client for matrix identification. }; +#define MTAG_ANIMATE_MOTION 0x8b929127 +#define MTAG_ANIMATE_TRANSFORM 0x5374188d +#define MTAG_SCENE_GRAPH 0xacc188f2 +#define MTAG_USE_TRANSFORM 0x35a3f7fb +#define MTAG_SVG_TRANSFORM 0x3479679e + struct FontMetrics { LONG Height; // Capitalised font height LONG LineSpacing; // Vertical advance from one line to the next @@ -1570,7 +1577,7 @@ struct vecPointInPath { DOUBLE X; DOUBLE Y; }; struct vecSubscribeInput { JTYPE Mask; FUNCTION * Callback; }; struct vecSubscribeKeyboard { FUNCTION * Callback; }; struct vecSubscribeFeedback { FM Mask; FUNCTION * Callback; }; -struct vecNewMatrix { struct VectorMatrix * Transform; }; +struct vecNewMatrix { struct VectorMatrix * Transform; LONG End; }; struct vecFreeMatrix { struct VectorMatrix * Matrix; }; inline ERR vecPush(APTR Ob, LONG Position) noexcept { @@ -1615,8 +1622,8 @@ inline ERR vecSubscribeFeedback(APTR Ob, FM Mask, FUNCTION * Callback) noexcept #define vecDebug(obj) Action(MT_VecDebug,(obj),0) -inline ERR vecNewMatrix(APTR Ob, struct VectorMatrix ** Transform) noexcept { - struct vecNewMatrix args = { (struct VectorMatrix *)0 }; +inline ERR vecNewMatrix(APTR Ob, struct VectorMatrix ** Transform, LONG End) noexcept { + struct vecNewMatrix args = { (struct VectorMatrix *)0, End }; ERR error = Action(MT_VecNewMatrix, (OBJECTPTR)Ob, &args); if (Transform) *Transform = args.Transform; return(error); diff --git a/scripts/vfx.fluid b/scripts/vfx.fluid index c70f5c117..09f935c63 100644 --- a/scripts/vfx.fluid +++ b/scripts/vfx.fluid @@ -153,7 +153,7 @@ vfx.zoomIn = function(Viewport, Seconds, Delay, CallbackOnCompletion) local state = { name = 'zoomIn', seconds = nz(Seconds, 1.0), delay = nz(Delay, 0), callback = CallbackOnCompletion } local err - err, state.matrix = Viewport.mtNewMatrix() + err, state.matrix = Viewport.mtNewMatrix(false) mVector.Translate(state.matrix, Viewport.width*0.5, Viewport.height*0.5) mVector.Scale(state.matrix, value, value) mVector.Translate(state.matrix, -Viewport.width*0.5, -Viewport.height*0.5) @@ -196,7 +196,7 @@ vfx.zoomOut = function(Viewport, Seconds, Delay, CallbackOnCompletion) local state = { name = 'zoomOut', seconds = nz(Seconds, 1.0), delay = nz(Delay, 0), callback = CallbackOnCompletion } local err - err, state.matrix = Viewport.mtNewMatrix() + err, state.matrix = Viewport.mtNewMatrix(false) mVector.Translate(state.matrix, Viewport.width*0.5, Viewport.height*0.5) mVector.Scale(state.matrix, value, value) mVector.Translate(state.matrix, -Viewport.width*0.5, -Viewport.height*0.5) @@ -251,7 +251,7 @@ vfx.shake = function(Viewport, Seconds, Delay, Intensity, CallbackOnCompletion) if (SHAKES < 1) then SHAKES = 1 end local err - err, state.matrix = Viewport.mtNewMatrix() + err, state.matrix = Viewport.mtNewMatrix(false) err, state.timerID = mSys.SubscribeTimer(1*vfx.fps, function(Subscriber, Elapsed, CurrentTime) if not Viewport.exists() then check(ERR_Terminate) end @@ -298,7 +298,7 @@ vfx.rotate = function(Viewport, Seconds, Delay, CallbackOnCompletion) local state = { name = 'rotate', seconds = nz(Seconds, 1.0), delay = nz(Delay, 0), callback = CallbackOnCompletion } local err - err, state.matrix = Viewport.mtNewMatrix() + err, state.matrix = Viewport.mtNewMatrix(false) err, state.timerID = mSys.SubscribeTimer(1*vfx.fps, function(Subscriber, Elapsed, CurrentTime) if not Viewport.exists() then check(ERR_Terminate) end @@ -333,7 +333,7 @@ end vfx.wipes.simple.init = function(State, Options) if (Options.rotate) then local err - err, State.matrix = State.clip_path.mtNewMatrix() + err, State.matrix = State.clip_path.mtNewMatrix(false) mVector.Rotate(State.matrix, Options.rotate, State.v_width * 0.5, State.v_height * 0.5) end end @@ -516,7 +516,7 @@ end vfx.wipes.shutter.init = function(State, Options) if (Options.rotate) then local err - err, State.matrix = State.clip_path.mtNewMatrix() + err, State.matrix = State.clip_path.mtNewMatrix(false) mVector.Rotate(State.matrix, Options.rotate, State.v_width * 0.5, State.v_height * 0.5) end end diff --git a/src/core/data_errors.c b/src/core/data_errors.c index 7ff15aa97..7e1f0b249 100644 --- a/src/core/data_errors.c +++ b/src/core/data_errors.c @@ -182,7 +182,7 @@ const CSTRING glMessages[LONG(ERR::END)+1] = { "It is not possible to perform the requested operation.", "Failed to resolve a linked library symbol.", "A function call failed.", - "Attempted to change a value that cannot be redefined.", + "Re-definition is not permitted.", "Attempted to set a numeric field with an incompatible value.", "Attempted to set a string field with an incompatible value.", "Attempted to set an object field with an incompatible value.", diff --git a/src/document/ui.cpp b/src/document/ui.cpp index c134f4696..c8e75b208 100644 --- a/src/document/ui.cpp +++ b/src/document/ui.cpp @@ -1172,7 +1172,7 @@ static ERR inputevent_button(objVectorViewport *Viewport, const InputEvent *Even if (!button->viewport->Matrices) { VectorMatrix *matrix; - vecNewMatrix(*button->viewport, &matrix); + vecNewMatrix(*button->viewport, &matrix, false); } const auto width = button->viewport->get(FID_Width); diff --git a/src/svg/animation.cpp b/src/svg/animation.cpp index aa957ced7..97e690027 100644 --- a/src/svg/animation.cpp +++ b/src/svg/animation.cpp @@ -142,7 +142,6 @@ double anim_base::get_numeric_value(objVector &Vector, FIELD Field) if ((accumulate) and (repeat_count)) { // Cumulative animation is not permitted for: // * The 'to animation' where 'from' is undefined. - // * Animations that do not repeat from_val += offset * repeat_index; to_val += offset * repeat_index; @@ -206,7 +205,7 @@ double anim_base::get_dimension(objVector &Vector, FIELD Field) // Rather than use distance, we're going to use the 'x' position as a lookup on the horizontal axis. // The paired y value then gives us the 'real' seek_to value. // The spline points are already sorted by the x value to make this easier. - + const double x = (seek >= 1.0) ? 1.0 : fmod(seek, 1.0 / double(std::ssize(spline_paths))) * std::ssize(spline_paths); LONG si; @@ -348,7 +347,7 @@ bool anim_base::started(double CurrentTime) const double elapsed = CurrentTime - start_time; if (elapsed < begin_offset) return false; } - + // Start/Reset linked animations for (auto &other : start_on_begin) { other->activate(); @@ -364,6 +363,8 @@ bool anim_base::started(double CurrentTime) void anim_base::next_frame(double CurrentTime) { + if (end_time) return; + const double elapsed = CurrentTime - start_time; seek = elapsed / duration; // A value between 0 and 1.0 @@ -396,7 +397,7 @@ static ERR parse_spline(APTR Path, LONG Index, LONG Command, double X, double Y, return ERR::Okay; } -static ERR set_anim_property(extSVG *Self, anim_base &Anim, objVector *Vector, XMLTag &Tag, ULONG Hash, const std::string_view Value) +static ERR set_anim_property(extSVG *Self, anim_base &Anim, XMLTag &Tag, ULONG Hash, const std::string_view Value) { switch (Hash) { case SVF_ID: @@ -404,6 +405,15 @@ static ERR set_anim_property(extSVG *Self, anim_base &Anim, objVector *Vector, X add_id(Self, Tag, Value); break; + case SVF_HREF: + case SVF_XLINK_HREF: { + OBJECTPTR ref_vector; + if (scFindDef(Self->Scene, Value.data(), &ref_vector) IS ERR::Okay) { + Anim.target_vector = ref_vector->UID; + } + break; + } + case SVF_ATTRIBUTENAME: // Name of the target attribute affected by the From and To values. Anim.target_attrib = Value; break; @@ -452,12 +462,12 @@ static ERR set_anim_property(extSVG *Self, anim_base &Anim, objVector *Vector, X // id.repeat(value): Reference to another animation, repeat when the given value is reached. // access-key: The animation starts when a keyboard key is pressed. // clock: A real-world clock time (not supported) - + if ("indefinite" IS Value) { Anim.begin_offset = std::numeric_limits::max(); break; } - + if (Value.ends_with(".begin")) { Anim.begin_offset = std::numeric_limits::max(); auto ref = Value.substr(0, Value.size()-6); @@ -479,7 +489,7 @@ static ERR set_anim_property(extSVG *Self, anim_base &Anim, objVector *Vector, X } break; } - + if ("access-key" IS Value) { // Start the animation when the user presses a key. Anim.begin_offset = std::numeric_limits::max(); break; @@ -696,11 +706,14 @@ static ERR motion_callback(objVector *Vector, LONG Index, LONG Cmd, double X, do return ERR::Okay; }; -void anim_motion::perform() +void anim_motion::perform(extSVG &SVG) { - double x1, y1, x2, y2, angle = -1; + POINT a, b; + double angle = -1; double seek_to = seek; + if ((end_time) and (!freeze)) return; + pf::ScopedObjectLock vector(target_vector, 1000); if (!vector.granted()) return; @@ -708,8 +721,7 @@ void anim_motion::perform() // animateMotion property. if ((mpath) or (not path.empty())) { - LONG new_timestamp; - vector->get(FID_PathTimestamp, &new_timestamp); + auto new_timestamp = vector->get(FID_PathTimestamp); if ((points.empty()) or (path_timestamp != new_timestamp)) { // Trace the path and store its points. Transforms are completely ignored when pulling the path from @@ -723,7 +735,7 @@ void anim_motion::perform() } else if ((vecTrace(*path, &call, 1.0, false) != ERR::Okay) or (points.empty())) return; - vector->get(FID_PathTimestamp, &path_timestamp); + path_timestamp = vector->get(FID_PathTimestamp); if ((auto_rotate IS ART::AUTO) or (auto_rotate IS ART::AUTO_REVERSE)) { precalc_angles(); @@ -739,10 +751,8 @@ void anim_motion::perform() LONG i; for (i=0; (i < std::ssize(distances)-1) and (distances[i+1] < dist_pos); i++); - x1 = points[i].x; - y1 = points[i].y; - x2 = points[i+1].x; - y2 = points[i+1].y; + a = points[i]; + b = points[i+1]; seek_to = (dist_pos - distances[i]) / (distances[i+1] - distances[i]); @@ -755,10 +765,8 @@ void anim_motion::perform() LONG i = F2T((std::ssize(points)-1) * seek); if (i >= std::ssize(points)-1) i = std::ssize(points) - 2; - x1 = points[i].x; - y1 = points[i].y; - x2 = points[i+1].x; - y2 = points[i+1].y; + a = points[i]; + b = points[i+1]; const double mod = 1.0 / double(points.size() - 1); seek_to = (seek >= 1.0) ? 1.0 : fmod(seek, mod) / mod; @@ -785,92 +793,342 @@ void anim_motion::perform() seek_to = (seek >= 1.0) ? 1.0 : fmod(seek, mod) / mod; } - read_numseq(values[i], { &x1, &y1 }); - read_numseq(values[i+1], { &x2, &y2 }); + read_numseq(values[i], { &a.x, &a.y }); + read_numseq(values[i+1], { &b.x, &b.y }); } else if (not from.empty()) { if (not to.empty()) { - read_numseq(from, { &x1, &y1 }); - read_numseq(to, { &x2, &y2 } ); + read_numseq(from, { &a.x, &a.y }); + read_numseq(to, { &b.x, &b.y } ); } else if (not by.empty()) { - read_numseq(from, { &x1, &y1 }); - read_numseq(by, { &x2, &y2 } ); - x2 += x1; - y2 += y1; + read_numseq(from, { &a.x, &a.y }); + read_numseq(by, { &b.x, &b.y } ); + b.x += a.x; + b.y += a.y; } else return; } else return; - if (not matrix) vecNewMatrix(*vector, &matrix); + // Note how the matrix is assigned to the end of the transform list so that it is executed last. This is a + // requirement of the SVG standard. It is important that the matrix is managed independently and not + // intermixed with other transforms. + + if (not matrix) { + vecNewMatrix(*vector, &matrix, true); + matrix->Tag = MTAG_ANIMATE_MOTION; + } vecResetMatrix(matrix); if (angle != -1) vecRotate(matrix, angle, 0, 0); - else if (auto_rotate IS ART::FIXED) vecRotate(matrix, rotate, 0, 0); + else if (auto_rotate IS ART::FIXED) vecRotate(matrix, rotate, 0, 0); if (calc_mode IS CMODE::DISCRETE) { - if (seek_to < 0.5) vecTranslate(matrix, x1, y1); - else vecTranslate(matrix, x2, y2); + if (seek_to < 0.5) vecTranslate(matrix, a.x, a.y); + else vecTranslate(matrix, b.x, b.y); } else { // CMODE::LINEAR - x1 = x1 + ((x2 - x1) * seek_to); - y1 = y1 + ((y2 - y1) * seek_to); - vecTranslate(matrix, x1, y1); + pf::POINT final { a.x + ((b.x - a.x) * seek_to), a.y + ((b.y - a.y) * seek_to) }; + vecTranslate(matrix, final.x, final.y); } } //******************************************************************************************************************** +// Note: SVG rules state that only one transformation matrix is active at any time, irrespective of however many +// elements are active for a vector. Multiple transformations are multiplicative by default. +// If a transform is in REPLACE mode, all prior transforms are overwritten, INCLUDING the vector's 'transform' attribute. -void anim_transform::perform() +void anim_transform::perform(extSVG &SVG) { - pf::ScopedObjectLock vector(target_vector, 1000); + double seek_to = seek; + + if ((end_time) and (!freeze)) return; + + pf::ScopedObjectLock vector(target_vector, 1000); if (vector.granted()) { - if (not matrix) vecNewMatrix(*vector, &matrix); + if (not matrix) { + if (auto im = SVG.Animatrix.find(target_vector); im != SVG.Animatrix.end()) { + matrix = im->second; + } + } + + if (not matrix) { + vecNewMatrix(*vector, &matrix, false); + matrix->Tag = MTAG_ANIMATE_TRANSFORM; + SVG.Animatrix[target_vector] = matrix; + } + + if (additive IS ADD::REPLACE) { + // Replace mode is a little tricky if the vector has a transform attribute applied to it. We want to + // override the existing transform, but we could cause problems if we were to permanently destroy + // that information. The solution we're taking is to create an inversion of the transform declaration + // in order to undo it. + + VectorMatrix *m = NULL; + for (m = vector->Matrices; (m); m=m->Next) { + if (m->Tag IS MTAG_SVG_TRANSFORM) { + double d = 1.0 / (m->ScaleX * m->ScaleY - m->ShearY * m->ShearX); + + double t0 = m->ScaleY * d; + matrix->ScaleY = m->ScaleX * d; + matrix->ShearY = -m->ShearY * d; + matrix->ShearX = -m->ShearX * d; + + double t4 = -m->TranslateX * t0 - m->TranslateY * matrix->ShearX; + matrix->TranslateY = -m->TranslateX * matrix->ShearY - m->TranslateY * matrix->ScaleY; + + matrix->ScaleX = t0; + matrix->TranslateX = t4; + break; + } + } + + if (!m) vecResetMatrix(matrix); + } + else {} // In the case of ADD::SUM, we are layering this transform on top of any previously declared animateTransforms switch(type) { - case AT::TRANSLATE: break; - case AT::SCALE: break; + case AT::TRANSLATE: { + POINT t_from = { 0, 0 }, t_to = { 0, 0 }; + + if (not values.empty()) { + LONG vi = F2T((values.size()-1) * seek); + if (vi >= std::ssize(values)-1) vi = std::ssize(values) - 2; + + read_numseq(values[vi], { &t_from.x, &t_from.y }); + read_numseq(values[vi+1], { &t_to.x, &t_to.y } ); + + const double mod = 1.0 / double(values.size() - 1); + seek_to = (seek >= 1.0) ? 1.0 : fmod(seek, mod) / mod; + } + else if (not from.empty()) { + read_numseq(from, { &t_from.x, &t_from.y }); + + if (not to.empty()) { + read_numseq(to, { &t_to.x, &t_to.y } ); + } + else if (not by.empty()) { + read_numseq(by, { &t_to.x, &t_to.y } ); + t_to.x += t_from.x; + t_to.y += t_from.y; + } + else break; + } + else if (not to.empty()) break; // SVG prohibits the use of a single 'to' value for transforms. + else if (not by.empty()) { // Placeholder; not correctly implemented + read_numseq(by, { &t_to.x, &t_to.y } ); + t_from = t_to; + } + else break; + + const POINT t_offset = t_to; + + if ((accumulate) and (repeat_count)) { + const POINT acc = t_offset * repeat_index; + t_from += acc; + t_to += acc; + } + + const double x = t_from.x + ((t_to.x - t_from.x) * seek_to); + double y = t_from.y + ((t_to.y - t_from.y) * seek_to); + vecTranslate(matrix, x, y); + break; + } + + case AT::SCALE: { + POINT t_from = { 0, 0 }, t_to = { 0, 0 }; + + if (not values.empty()) { + LONG vi = F2T((values.size()-1) * seek); + if (vi >= std::ssize(values)-1) vi = std::ssize(values) - 2; + + read_numseq(values[vi], { &t_from.x, &t_from.y }); + read_numseq(values[vi+1], { &t_to.x, &t_to.y } ); + + if (!t_from.y) t_from.y = t_from.x; + + const double mod = 1.0 / double(values.size() - 1); + seek_to = (seek >= 1.0) ? 1.0 : fmod(seek, mod) / mod; + } + else if (not from.empty()) { + read_numseq(from, { &t_from.x, &t_from.y }); + if (!t_from.y) t_from.y = t_from.x; + + if (not to.empty()) { + read_numseq(to, { &t_to.x, &t_to.y } ); + } + else if (not by.empty()) { + read_numseq(by, { &t_to.x, &t_to.y } ); + t_to.x += t_from.x; + t_to.y += t_from.y; + } + else break; + } + else if (not to.empty()) break; // SVG prohibits the use of a single 'to' value for transforms. + else if (not by.empty()) { // Placeholder; not correctly implemented + read_numseq(by, { &t_to.x, &t_to.y } ); + t_from = t_to; + } + else break; + + if (!t_to.y) t_to.y = t_to.x; + + const POINT t_offset = t_to; + + if ((accumulate) and (repeat_count)) { + const POINT acc = t_offset * repeat_index; + t_from += acc; + t_to += acc; + } + + const double x = t_from.x + ((t_to.x - t_from.x) * seek_to); + double y = t_from.y + ((t_to.y - t_from.y) * seek_to); + if (!y) y = x; + vecScale(matrix, x, y); + break; + } + case AT::ROTATE: { - double from_angle = 0, from_cx = 0, from_cy = 0, to_angle = 0, to_cx = 0, to_cy = 0; + ROTATE r_from, r_to; if (not values.empty()) { LONG vi = F2T((values.size()-1) * seek); - if (vi >= LONG(values.size())-1) vi = values.size() - 2; + if (vi >= std::ssize(values)-1) vi = std::ssize(values) - 2; - read_numseq(values[vi], { &from_angle, &from_cx, &from_cy }); - read_numseq(values[vi+1], { &to_angle, &to_cx, &to_cy } ); + read_numseq(values[vi], { &r_from.angle, &r_from.cx, &r_from.cy }); + read_numseq(values[vi+1], { &r_to.angle, &r_to.cx, &r_to.cy } ); + + const double mod = 1.0 / double(values.size() - 1); + seek_to = (seek >= 1.0) ? 1.0 : fmod(seek, mod) / mod; } else if (not from.empty()) { - read_numseq(from, { &from_angle, &from_cx, &from_cy }); + read_numseq(from, { &r_from.angle, &r_from.cx, &r_from.cy }); if (not to.empty()) { - read_numseq(to, { &to_angle, &to_cx, &to_cy } ); + read_numseq(to, { &r_to.angle, &r_to.cx, &r_to.cy } ); } else if (not by.empty()) { - read_numseq(by, { &to_angle, &to_cx, &to_cy } ); - to_angle += from_angle; - to_cx += from_cx; - to_cy += from_cy; + read_numseq(by, { &r_to.angle, &r_to.cx, &r_to.cy } ); + r_to.angle += r_from.angle; + r_to.cx += r_from.cx; + r_to.cy += r_from.cy; } else break; } + else if (not to.empty()) break; // SVG prohibits the use of a single 'to' value for transforms. + else if (not by.empty()) { // Placeholder; not correctly implemented + read_numseq(by, { &r_to.angle, &r_to.cx, &r_to.cy } ); + r_from = r_to; + } else break; - double mod = 1.0 / (double)(values.size() - 1); - double ratio; - if (seek == 1.0) ratio = 1.0; - else ratio = fmod(seek, mod) / mod; + const auto r_offset = r_to; - double new_angle = from_angle + ((to_angle - from_angle) * ratio); - double new_cx = from_cx + ((to_cx - from_cx) * ratio); - double new_cy = from_cy + ((to_cy - from_cy) * ratio); + if ((accumulate) and (repeat_count)) { + r_from += r_offset * repeat_index; + r_to += r_offset * repeat_index; + } + + const ROTATE r_new = { + r_from.angle + ((r_to.angle - r_from.angle) * seek_to), + r_from.cx + ((r_to.cx - r_from.cx) * seek_to), + r_from.cy + ((r_to.cy - r_from.cy) * seek_to) + }; + + vecRotate(matrix, r_new.angle, r_new.cx, r_new.cy); + break; + } + + case AT::SKEW_X: { + double t_from = 0, t_to = 0; + + if (not values.empty()) { + LONG vi = F2T((values.size()-1) * seek); + if (vi >= std::ssize(values)-1) vi = std::ssize(values) - 2; + + read_numseq(values[vi], { &t_from }); + read_numseq(values[vi+1], { &t_to } ); + + const double mod = 1.0 / double(values.size() - 1); + seek_to = (seek >= 1.0) ? 1.0 : fmod(seek, mod) / mod; + } + else if (not from.empty()) { + read_numseq(from, { &t_from }); + + if (not to.empty()) { + read_numseq(to, { &t_to } ); + } + else if (not by.empty()) { + read_numseq(by, { &t_to } ); + t_to += t_from; + } + else break; + } + else if (not to.empty()) break; // SVG prohibits the use of a single 'to' value for transforms. + else if (not by.empty()) { // Placeholder; not correctly implemented + read_numseq(by, { &t_to } ); + t_from = t_to; + } + else break; + + const double t_offset = t_to; + + if ((accumulate) and (repeat_count)) { + const double acc = t_offset * repeat_index; + t_from += acc; + t_to += acc; + } + + const double x = t_from + ((t_to - t_from) * seek_to); + vecSkew(matrix, x, 0); + break; + } + + case AT::SKEW_Y: { + double t_from = 0, t_to = 0; + + if (not values.empty()) { + LONG vi = F2T((values.size()-1) * seek); + if (vi >= std::ssize(values)-1) vi = std::ssize(values) - 2; + + read_numseq(values[vi], { &t_from }); + read_numseq(values[vi+1], { &t_to } ); + + const double mod = 1.0 / double(values.size() - 1); + seek_to = (seek >= 1.0) ? 1.0 : fmod(seek, mod) / mod; + } + else if (not from.empty()) { + read_numseq(from, { &t_from }); + + if (not to.empty()) { + read_numseq(to, { &t_to } ); + } + else if (not by.empty()) { + read_numseq(by, { &t_to } ); + t_to += t_from; + } + else break; + } + else if (not to.empty()) break; // SVG prohibits the use of a single 'to' value for transforms. + else if (not by.empty()) { // Placeholder; not correctly implemented + read_numseq(by, { &t_to } ); + t_from = t_to; + } + else break; + + const double t_offset = t_to; + + if ((accumulate) and (repeat_count)) { + const double acc = t_offset * repeat_index; + t_from += acc; + t_to += acc; + } - vecResetMatrix(matrix); - vecRotate(matrix, new_angle, new_cx, new_cy); + const double y = t_from + ((t_to - t_from) * seek_to); + vecSkew(matrix, 0, y); break; } - case AT::SKEW_X: break; - case AT::SKEW_Y: break; + default: break; } } @@ -881,8 +1139,10 @@ void anim_transform::perform() // // -void anim_value::perform() +void anim_value::perform(extSVG &SVG) { + if ((end_time) and (!freeze)) return; + pf::ScopedObjectLock vector(target_vector, 1000); if (vector.granted()) { // Determine the type of the attribute that we're targeting, then interpolate the value and set it. @@ -900,59 +1160,102 @@ void anim_value::perform() break; } + case SVF_FILL_OPACITY: { + auto val = get_numeric_value(**vector, FID_FillOpacity); + vector->set(FID_FillOpacity, val); + break; + } + case SVF_STROKE: { auto val = get_colour_value(**vector, FID_StrokeColour); vector->setArray(FID_StrokeColour, (float *)&val, 4); break; } - case SVF_OPACITY: { - auto val = get_numeric_value(**vector, FID_Opacity); - vector->set(FID_Opacity, val); + case SVF_STROKE_WIDTH: + vector->set(FID_StrokeWidth, get_numeric_value(**vector, FID_StrokeWidth)); break; - } - case SVF_CX: { - auto val = get_dimension(**vector, FID_CX); - vector->set(FID_CX, val); + case SVF_OPACITY: + vector->set(FID_Opacity, get_numeric_value(**vector, FID_Opacity)); break; - } - case SVF_CY: { - auto val = get_dimension(**vector, FID_CY); - vector->set(FID_CY, val); + case SVF_CX: + vector->set(FID_CX, get_dimension(**vector, FID_CX)); + break; + + case SVF_CY: + vector->set(FID_CY, get_dimension(**vector, FID_CY)); + break; + + case SVF_X1: + vector->set(FID_X1, get_dimension(**vector, FID_X1)); + break; + + case SVF_Y1: + vector->set(FID_Y1, get_dimension(**vector, FID_Y1)); + break; + + case SVF_X2: + vector->set(FID_X2, get_dimension(**vector, FID_X2)); + break; + + case SVF_Y2: + vector->set(FID_Y2, get_dimension(**vector, FID_Y2)); break; - } case SVF_X: { - auto val = get_dimension(**vector, FID_X); - vector->set(FID_X, val); + if (vector->Class->ClassID IS ID_VECTORGROUP) { + // Special case: SVG groups don't have an (x,y) position, but can declare one in the form of a + // transform. Refer to xtag_use() for a working example as to why. + + VectorMatrix *m; + for (m=vector->Matrices; (m) and (m->Tag != MTAG_SVG_TRANSFORM); m=m->Next); + + if (!m) { + vecNewMatrix(*vector, &m, false); + m->Tag = MTAG_SVG_TRANSFORM; + } + + if (m) { + m->TranslateX = get_dimension(**vector, FID_X); + vecFlushMatrix(m); + } + } + else vector->set(FID_X, get_dimension(**vector, FID_X)); break; } case SVF_Y: { - auto val = get_dimension(**vector, FID_Y); - vector->set(FID_Y, val); + if (vector->Class->ClassID IS ID_VECTORGROUP) { + VectorMatrix *m; + for (m=vector->Matrices; (m) and (m->Tag != MTAG_SVG_TRANSFORM); m=m->Next); + + if (!m) { + vecNewMatrix(*vector, &m, false); + m->Tag = MTAG_SVG_TRANSFORM; + } + + if (m) { + m->TranslateY = get_dimension(**vector, FID_Y); + vecFlushMatrix(m); + } + } + else vector->set(FID_Y, get_dimension(**vector, FID_Y)); break; } - case SVF_WIDTH: { - auto val = get_dimension(**vector, FID_Width); - vector->set(FID_Width, val); + case SVF_WIDTH: + vector->set(FID_Width, get_dimension(**vector, FID_Width)); break; - } - case SVF_HEIGHT: { - auto val = get_dimension(**vector, FID_Height); - vector->set(FID_Height, val); + case SVF_HEIGHT: + vector->set(FID_Height, get_dimension(**vector, FID_Height)); break; - } - case SVF_VISIBILITY: { - auto val = get_string(); - vector->set(FID_Visibility, val); + case SVF_VISIBILITY: + vector->set(FID_Visibility, get_string()); break; - } } } } @@ -961,21 +1264,26 @@ void anim_value::perform() static ERR animation_timer(extSVG *SVG, LARGE TimeElapsed, LARGE CurrentTime) { + pf::Log log(__FUNCTION__); + if (SVG->Animations.empty()) { - pf::Log log(__FUNCTION__); log.msg("All animations processed, timer suspended."); return ERR::Terminate; } - for (auto &record : SVG->Animations) { - std::visit([](auto &&anim) { - if (anim.end_time) return; + // All transform matrices need to be reset on each cycle. + for (auto &matrix : SVG->Animatrix) { + vecResetMatrix(matrix.second); + } + + for (auto &record : SVG->Animations) { + std::visit([SVG](auto &&anim) { double current_time = double(PreciseTime()) / 1000000.0; if (not anim.started(current_time)) return; anim.next_frame(current_time); - anim.perform(); + anim.perform(*SVG); }, record); } diff --git a/src/svg/animation.h b/src/svg/animation.h index cdb0c542b..32bc7a049 100644 --- a/src/svg/animation.h +++ b/src/svg/animation.h @@ -41,6 +41,33 @@ enum class RST : char { // Restart //******************************************************************************************************************** +struct ROTATE { + double angle = 0, cx = 0, cy = 0; + + constexpr ROTATE & operator += (const ROTATE &Other) { + angle += Other.angle; + cx += Other.cx; + cy += Other.cy; + return *this; + } + + constexpr ROTATE & operator += (const DOUBLE &Angle) { + angle += Angle; + return *this; + } + + constexpr ROTATE & operator *= (const DOUBLE &Angle) { + angle *= Angle; + return *this; + } +}; + +constexpr ROTATE operator * (const ROTATE &lhs, const DOUBLE &Num) { + return ROTATE { lhs.angle * Num, lhs.cx, lhs.cy }; +} + +//******************************************************************************************************************** + template double dist(const pf::POINT &A, const pf::POINT &B) { if (A == B) return 0; @@ -71,6 +98,8 @@ class anim_base { spline_path(SPLINE_POINTS pPoints) : points(pPoints) { } }; + // Variables + std::vector values; // Set of discrete values that override 'from', 'to', 'by' std::vector timing; // Key times. Ignored if duration < 0 std::vector key_points; // Key points @@ -105,6 +134,8 @@ class anim_base { bool freeze = false; bool accumulate = false; + // Functionality + anim_base(OBJECTID pTarget) : target_vector(pTarget) { } double get_total_dist(); @@ -134,7 +165,8 @@ class anim_base { } } - virtual void perform() = 0; + virtual void perform(class extSVG &) = 0; + virtual bool is_valid() { if (!values.empty()) return true; if ((!to.empty()) or (!by.empty())) return true; @@ -147,8 +179,21 @@ class anim_base { class anim_transform : public anim_base { public: AT type; + anim_transform(OBJECTID pTarget) : anim_base(pTarget) { } - void perform(); + + void perform(extSVG &); + + std::string type_name() { + switch (type) { + case AT::TRANSLATE: return "translate"; + case AT::SCALE: return "scale"; + case AT::ROTATE: return "rotate"; + case AT::SKEW_X: return "skewX"; + case AT::SKEW_Y: return "skewY"; + default: return "?"; + } + } }; //******************************************************************************************************************** @@ -167,7 +212,7 @@ class anim_motion : public anim_base { calc_mode = CMODE::PACED; } - void perform(); + void perform(extSVG &); void precalc_angles(); double get_total_dist(); @@ -185,7 +230,7 @@ class anim_motion : public anim_base { class anim_value : public anim_base { public: anim_value(OBJECTID pTarget) : anim_base(pTarget) { } - void perform(); + void perform(extSVG &); }; //******************************************************************************************************************** diff --git a/src/svg/parser.cpp b/src/svg/parser.cpp index ec67b6268..557219ab7 100644 --- a/src/svg/parser.cpp +++ b/src/svg/parser.cpp @@ -209,7 +209,7 @@ static void xtag_clippath(extSVG *Self, XMLTag &Tag) if (NewObject(ID_VECTORCLIP, &clip) IS ERR::Okay) { clip->setFields(fl::Owner(Self->Scene->UID), fl::Name("SVGClip")); - if (!transform.empty()) parse_transform(clip, transform); + if (!transform.empty()) parse_transform(clip, transform, MTAG_SVG_TRANSFORM); if (!units.empty()) { if (StrMatch("userSpaceOnUse", units) IS ERR::Okay) clip->set(FID_Units, LONG(VUNIT::USERSPACE)); @@ -292,7 +292,7 @@ static void xtag_mask(extSVG *Self, XMLTag &Tag) fl::Flags(VCLF::APPLY_FILLS|VCLF::APPLY_STROKES), fl::Units(units)); - if (!transform.empty()) parse_transform(clip, transform); + if (!transform.empty()) parse_transform(clip, transform, MTAG_SVG_TRANSFORM); if (InitObject(clip) IS ERR::Okay) { svgState state(Self); @@ -2310,7 +2310,7 @@ static void xtag_use(extSVG *Self, svgState &State, XMLTag &Tag, OBJECTPTR Paren } if ((!tx.empty()) or (!ty.empty())) { - parse_transform(group, "translate(" + std::to_string(tx) + " " + std::to_string(ty) + ")"); + parse_transform(group, "translate(" + std::to_string(tx) + " " + std::to_string(ty) + ")", MTAG_USE_TRANSFORM); } if (group->init() != ERR::Okay) { FreeResource(group); return; } @@ -2422,7 +2422,12 @@ static void xtag_group(extSVG *Self, svgState &State, XMLTag &Tag, OBJECTPTR Par for (auto find_anim=Tag.Children.begin(); find_anim != Tag.Children.end(); ) { if ((find_anim->isTag()) and (iequals("animate", find_anim->name()))) { - if (auto attrib = find_anim->attrib("attributeName")) { + if ((find_anim->attrib("href")) or (find_anim->attrib("xlink:href"))) { + // Ignore animation tags that specify a link. + find_anim++; + continue; + } + else if (auto attrib = find_anim->attrib("attributeName")) { for (auto &clone_to : Tag.Children) { if (clone_to.isTag() and (not iequals("animate", clone_to.name()))) { if (!clone_to.attrib(*attrib)) { @@ -2611,7 +2616,7 @@ static ERR xtag_animate_transform(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) Self->Animated = true; - auto &new_anim = Self->Animations.emplace_front(anim_transform { Parent->UID }); + auto &new_anim = Self->Animations.emplace_back(anim_transform { Parent->UID }); auto &anim = std::get(new_anim); for (LONG a=1; a < std::ssize(Tag.Attribs); a++) { @@ -2630,24 +2635,26 @@ static ERR xtag_animate_transform(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) break; default: - set_anim_property(Self, anim, (objVector *)Parent, Tag, hash, value); + set_anim_property(Self, anim, Tag, hash, value); break; } } - if (!anim.is_valid()) Self->Animations.erase_after(Self->Animations.before_begin()); + if (!anim.is_valid()) Self->Animations.pop_back(); return ERR::Okay; } //******************************************************************************************************************** -// The ‘animate’ element is used to animate a single attribute or property over time. For example, to make a +// The 'animate' element is used to animate a single attribute or property over time. For example, to make a // rectangle repeatedly fade away over 5 seconds: +// +// NOTE: Also see xtag_group() which duplicates elements for technical reasons. static ERR xtag_animate(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) { Self->Animated = true; - auto &new_anim = Self->Animations.emplace_front(anim_value { Parent->UID }); + auto &new_anim = Self->Animations.emplace_back(anim_value { Parent->UID }); auto &anim = std::get(new_anim); for (LONG a=1; a < std::ssize(Tag.Attribs); a++) { @@ -2657,12 +2664,12 @@ static ERR xtag_animate(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) auto hash = StrHash(Tag.Attribs[a].Name); switch (hash) { default: - set_anim_property(Self, anim, (objVector *)Parent, Tag, hash, value); + set_anim_property(Self, anim, Tag, hash, value); break; } } - if (!anim.is_valid()) Self->Animations.erase_after(Self->Animations.before_begin()); + if (!anim.is_valid()) Self->Animations.pop_back(); return ERR::Okay; } @@ -2673,7 +2680,7 @@ static ERR xtag_set(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) { Self->Animated = true; - auto &new_anim = Self->Animations.emplace_front(anim_value { Parent->UID }); + auto &new_anim = Self->Animations.emplace_back(anim_value { Parent->UID }); auto &anim = std::get(new_anim); for (LONG a=1; a < std::ssize(Tag.Attribs); a++) { @@ -2683,12 +2690,12 @@ static ERR xtag_set(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) auto hash = StrHash(Tag.Attribs[a].Name); switch (hash) { default: - set_anim_property(Self, anim, (objVector *)Parent, Tag, hash, value); + set_anim_property(Self, anim, Tag, hash, value); break; } } - if (!anim.is_valid()) Self->Animations.erase_after(Self->Animations.before_begin()); + if (!anim.is_valid()) Self->Animations.pop_back(); return ERR::Okay; } @@ -2700,7 +2707,7 @@ static ERR xtag_animate_colour(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) { Self->Animated = true; - auto &new_anim = Self->Animations.emplace_front(anim_value { Parent->UID }); + auto &new_anim = Self->Animations.emplace_back(anim_value { Parent->UID }); auto &anim = std::get(new_anim); for (LONG a=1; a < std::ssize(Tag.Attribs); a++) { @@ -2710,12 +2717,12 @@ static ERR xtag_animate_colour(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) auto hash = StrHash(Tag.Attribs[a].Name); switch (hash) { default: - set_anim_property(Self, anim, (objVector *)Parent, Tag, hash, value); + set_anim_property(Self, anim, Tag, hash, value); break; } } - if (!anim.is_valid()) Self->Animations.erase_after(Self->Animations.before_begin()); + if (!anim.is_valid()) Self->Animations.pop_back(); return ERR::Okay; } @@ -2726,7 +2733,7 @@ static ERR xtag_animate_motion(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) { Self->Animated = true; - auto &new_anim = Self->Animations.emplace_front(anim_motion { Parent->UID }); + auto &new_anim = Self->Animations.emplace_back(anim_motion { Parent->UID }); auto &anim = std::get(new_anim); for (LONG a=1; a < std::ssize(Tag.Attribs); a++) { @@ -2769,7 +2776,7 @@ static ERR xtag_animate_motion(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) case SVF_ORIGIN: break; // Officially serves no purpose. default: - set_anim_property(Self, anim, (objVector *)Parent, Tag, hash, value); + set_anim_property(Self, anim, Tag, hash, value); break; } } @@ -2792,7 +2799,7 @@ static ERR xtag_animate_motion(extSVG *Self, XMLTag &Tag, OBJECTPTR Parent) } } - if (!anim.is_valid()) Self->Animations.erase_after(Self->Animations.before_begin()); + if (!anim.is_valid()) Self->Animations.pop_back(); return ERR::Okay; } @@ -3395,7 +3402,7 @@ static ERR set_property(extSVG *Self, objVector *Vector, ULONG Hash, XMLTag &Tag else Vector->set(FID_Fill, StrValue); break; - case SVF_TRANSFORM: parse_transform(Vector, StrValue); break; + case SVF_TRANSFORM: parse_transform(Vector, StrValue, MTAG_SVG_TRANSFORM); break; case SVF_STROKE_DASHARRAY: Vector->set(FID_DashArray, StrValue); break; case SVF_OPACITY: Vector->set(FID_Opacity, StrValue); break; diff --git a/src/svg/svg.cpp b/src/svg/svg.cpp index 60a8f80bf..c3353f3f5 100644 --- a/src/svg/svg.cpp +++ b/src/svg/svg.cpp @@ -19,7 +19,7 @@ Relevant SVG reference manuals: #include #include #include -#include +#include #include #include #include @@ -79,7 +79,8 @@ class extSVG : public objSVG { STRING Folder; std::string Colour = "rgb(0,0,0)"; // Default colour, used for 'currentColor' references OBJECTPTR Viewport; // First viewport (the tag) to be created on parsing the SVG document. - std::forward_list> Animations; + std::list> Animations; // NB: Pointer stability is a container requirement + std::map Animatrix; // For animated transforms, a vector may have one matrix only. std::vector> Links; std::vector Inherit; TIMER AnimationTimer; diff --git a/src/svg/tests/animation/w3-animate-elem-24-t.svg b/src/svg/tests/animation/w3-animate-elem-24-t.svg new file mode 100644 index 000000000..89f1d396a --- /dev/null +++ b/src/svg/tests/animation/w3-animate-elem-24-t.svg @@ -0,0 +1,35 @@ + + + + + + + + Text from 0s to 1s + Text at 4s + Text at 7s + + + + + It's alive! + It's alive! + It's alive! + + + + + + + + It's alive! + + + + + + diff --git a/src/svg/tests/animation/w3-animate-elem-25-t.svg b/src/svg/tests/animation/w3-animate-elem-25-t.svg new file mode 100644 index 000000000..310e25144 --- /dev/null +++ b/src/svg/tests/animation/w3-animate-elem-25-t.svg @@ -0,0 +1,29 @@ + + + + Test animation options for specifying the target attribute/property. + + 0-3 sec. + + at 6 sec. + + + + + 0-6 sec. + + at 9 sec. + + + + + + diff --git a/src/svg/tests/animation/w3-animate-elem-26-t.svg b/src/svg/tests/animation/w3-animate-elem-26-t.svg new file mode 100644 index 000000000..8b61932c6 --- /dev/null +++ b/src/svg/tests/animation/w3-animate-elem-26-t.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + anim. 1 + + + + + + + + + + anim. 2 + + diff --git a/src/svg/tests/animation/w3-animate-elem-27-t.svg b/src/svg/tests/animation/w3-animate-elem-27-t.svg new file mode 100644 index 000000000..e7c6f9ec5 --- /dev/null +++ b/src/svg/tests/animation/w3-animate-elem-27-t.svg @@ -0,0 +1,35 @@ + + + + Test animation options for specifying the target element. + + + 0 to 3 sec. + + at 6 sec. + + + + + + 0 to 6 sec. + + at 9 sec. + + + + + + + diff --git a/src/svg/tests/animation/w3-animate-elem-28-t.svg b/src/svg/tests/animation/w3-animate-elem-28-t.svg new file mode 100644 index 000000000..2f4d1c206 --- /dev/null +++ b/src/svg/tests/animation/w3-animate-elem-28-t.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/svg/tests/animation/w3-animate-elem-29-b.svg b/src/svg/tests/animation/w3-animate-elem-29-b.svg new file mode 100644 index 000000000..e83921d30 --- /dev/null +++ b/src/svg/tests/animation/w3-animate-elem-29-b.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + Fade in + + + + + Fade out + + diff --git a/src/svg/tests/animation/w3-animate-elem-30-t.svg b/src/svg/tests/animation/w3-animate-elem-30-t.svg new file mode 100644 index 000000000..0756002b4 --- /dev/null +++ b/src/svg/tests/animation/w3-animate-elem-30-t.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/svg/tests/animation/w3-animate-elem-80-t.svg b/src/svg/tests/animation/w3-animate-elem-80-t.svg new file mode 100644 index 000000000..c21c6c3d5 --- /dev/null +++ b/src/svg/tests/animation/w3-animate-elem-80-t.svg @@ -0,0 +1,228 @@ + + + + + <animateTransform> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type=rotate + + + + + + + + + + + + + + + + + + + type=skewX + + + + + + + + + + + + + + + + + + + + + + type=skewY + + + + + + + + + + + + + + + + + + + + + + + type=scale + (sx and sy) + + + + + + + + + + + + + + + + + + + + + + + + + + + type=rotate + (with cx/cy) + + + + + + + + + + + + + + + + + + + + + + + + + + + type=translate + (tx only) + + + + + + + + + + + + + + + + + + + + + + + + + + + type=translate + (tx and ty) + + + + + + + + + + + + + + + + + + + + + + + type=scale + (sx only) + + + diff --git a/src/svg/tests/test-svg.fluid b/src/svg/tests/test-svg.fluid index 2ea8f0698..7d3735105 100644 --- a/src/svg/tests/test-svg.fluid +++ b/src/svg/tests/test-svg.fluid @@ -59,12 +59,12 @@ function testWave() hashTestSVG('paths/wave.svg', 0xc462fb58) end function testChevrons() hashTestSVG('patterns/chevrons.svg', 0x42a1be4b) end function testComplex() hashTestSVG('patterns/complex.svg', 0x3a30b99a) end -function testDiamonds() hashTestSVG('patterns/diamonds.svg', 0xe14ac1e8) end +function testDiamonds() hashTestSVG('patterns/diamonds.svg', 0x4642c53a) end function testDimple() hashTestSVG('patterns/dimple.svg', 0x1806e6a0) end -function testDot() hashTestSVG('patterns/dot.svg', 0x13252bb4) end +function testDot() hashTestSVG('patterns/dot.svg', 0x6736bc6c) end function testHoneycomb() hashTestSVG('patterns/honeycomb.svg', 0x7b678ada) end function testSnake() hashTestSVG('patterns/snake.svg', 0xbb7d40f0) end -function testVStripes() hashTestSVG('patterns/vstripes.svg', 0xc8ab997e) end +function testVStripes() hashTestSVG('patterns/vstripes.svg', 0x2f85dc21) end function testCoarsePaper() hashTestSVG('filters/coarse_paper.svg', 0xc4158cf1) end function testComposite() hashTestSVG('filters/composite.svg', 0x66a96a50) end @@ -125,7 +125,7 @@ function testBottleTree() hashTestSVG('images/bottletree.svg', 0xd7fb84f function testButton() hashTestSVG('images/button.svg', 0x0e8f5a31) end function testClock() hashTestSVG('images/clock.svg', 0xe5ad43cb) end function testIceCube() hashTestSVG('images/icecube.svg', 0x86d23579) end -function testTiger() hashTestSVG('images/tiger.svg', 0x8e6b884e) end +function testTiger() hashTestSVG('images/tiger.svg', 0xa7b2c808) end function testPod() hashTestSVG('images/pod.svg', 0x9a0fbeda) end function testClip() hashTestSVG('masks/clip.svg', 0x209c16aa) end @@ -138,8 +138,8 @@ function testW3MasksIntro() hashTestSVG('masks/w3-masking-intro-01-f.svg', function testW3Masks1() hashTestSVG('masks/w3-masking-mask-01-b.svg', 0x74e684d6) end function testW3Masks2() hashTestSVG('masks/w3-masking-mask-02-f.svg', 0x9f518e5f) end function testW3MasksOpacity() hashTestSVG('masks/w3-masking-opacity-01-b.svg', -1) end -function testW3MasksPath1() hashTestSVG('masks/w3-masking-path-01-b.svg', 0xddc00e40) end -function testW3MasksPath2() hashTestSVG('masks/w3-masking-path-02-b.svg', 0x33811df4) end +function testW3MasksPath1() hashTestSVG('masks/w3-masking-path-01-b.svg', 0x792ed4f4) end +function testW3MasksPath2() hashTestSVG('masks/w3-masking-path-02-b.svg', 0x7676e492) end function testW3MasksPath3() hashTestSVG('masks/w3-masking-path-03-b.svg', 0x97f20e58) end function testW3MasksPath4() hashTestSVG('masks/w3-masking-path-04-b.svg', 0x74da608c) end function testW3MasksPath5() hashTestSVG('masks/w3-masking-path-05-f.svg', 0x2ad5769b) end diff --git a/src/svg/utility.cpp b/src/svg/utility.cpp index b08c35b77..b4eadcd76 100644 --- a/src/svg/utility.cpp +++ b/src/svg/utility.cpp @@ -243,12 +243,13 @@ static CSTRING folder(extSVG *Self) //******************************************************************************************************************** -static void parse_transform(objVector *Vector, const std::string Value) +static void parse_transform(objVector *Vector, const std::string Value, LONG Tag) { if ((Vector->Class->BaseClassID IS ID_VECTOR) and (!Value.empty())) { VectorMatrix *matrix; - if (vecNewMatrix((objVector *)Vector, &matrix) IS ERR::Okay) { + if (vecNewMatrix((objVector *)Vector, &matrix, false) IS ERR::Okay) { vecParseTransform(matrix, Value.c_str()); + matrix->Tag = Tag; } else { pf::Log log(__FUNCTION__); @@ -399,17 +400,14 @@ inline std::string_view next_value(const std::string_view Value) // The parser will break once the string value terminates, or an invalid character is encountered. Parsed characters // include: 0 - 9 , ( ) - + SPACE -static std::string_view read_numseq(std::string_view String, std::initializer_list Value) +template std::string_view read_numseq(std::string_view String, std::initializer_list Value) { - for (DOUBLE *v : Value) { + for (T *v : Value) { String = next_value(String); - DOUBLE num; + T num; auto [ next, error ] = std::from_chars(String.data(), String.data() + String.size(), num); - - if ((!num) and ((!next) or (String IS next))) { // Invalid character or end-of-stream check. - return String; - } + if (error != std::errc()) return String; String = std::string_view(next, String.data() + String.size() - next); *v = num; } diff --git a/src/vector/idl.h b/src/vector/idl.h index 67e79e67e..cb51c0287 100644 --- a/src/vector/idl.h +++ b/src/vector/idl.h @@ -1,2 +1,2 @@ #undef MOD_IDL -#define MOD_IDL "s.GradientStop:dOffset,eRGB:FRGB\ns.Transition:dOffset,sTransform\ns.VectorPoint:dX,dY,ucBit,ucBit\ns.VectorPainter:oPattern,oImage,oGradient,eColour:FRGB\ns.PathCommand:lType,ucLargeArc,ucSweep,ucPad1,dX,dY,dAbsX,dAbsY,dX2,dY2,dX3,dY3,dAngle\ns.VectorMatrix:pNext:VectorMatrix,oVector,dScaleX,dShearY,dShearX,dScaleY,dTranslateX,dTranslateY\ns.FontMetrics:lHeight,lLineSpacing,lAscent,lDescent\ns.MergeSource:lSourceType,oEffect\nc.ARC:LARGE=0x1,SWEEP=0x2\nc.ARF:MEET=0x40,NONE=0x100,SLICE=0x80,X_MAX=0x4,X_MID=0x2,X_MIN=0x1,Y_MAX=0x20,Y_MID=0x10,Y_MIN=0x8\nc.CM:BRIGHTNESS=0x6,COLOURISE=0x9,CONTRAST=0x5,DESATURATE=0x8,HUE=0x7,HUE_ROTATE=0x3,LUMINANCE_ALPHA=0x4,MATRIX=0x1,NONE=0x0,SATURATE=0x2\nc.CMP:ALL=0xffffffff,ALPHA=0x3,BLUE=0x2,GREEN=0x1,RED=0x0\nc.EM:DUPLICATE=0x1,NONE=0x3,WRAP=0x2\nc.FM:CHILD_HAS_FOCUS=0x4,HAS_FOCUS=0x2,LOST_FOCUS=0x8,PATH_CHANGED=0x1\nc.LS:DISTANT=0x0,POINT=0x2,SPOT=0x1\nc.LT:DIFFUSE=0x0,SPECULAR=0x1\nc.MOP:DILATE=0x1,ERODE=0x0\nc.OP:ARITHMETIC=0x5,ATOP=0x3,BURN=0xe,CONTRAST=0xc,DARKEN=0x9,DIFFERENCE=0x11,DODGE=0xd,EXCLUSION=0x12,HARD_LIGHT=0xf,IN=0x1,INVERT=0xb,INVERT_RGB=0xa,LIGHTEN=0x8,MINUS=0x14,MULTIPLY=0x7,OUT=0x2,OVER=0x0,OVERLAY=0x15,PLUS=0x13,SCREEN=0x6,SOFT_LIGHT=0x10,SUBTRACT=0x14,XOR=0x4\nc.PE:Arc=0x11,ArcRel=0x12,ClosePath=0x13,Curve=0x9,CurveRel=0xa,HLine=0x5,HLineRel=0x6,Line=0x3,LineRel=0x4,Move=0x1,MoveRel=0x2,QuadCurve=0xd,QuadCurveRel=0xe,QuadSmooth=0xf,QuadSmoothRel=0x10,Smooth=0xb,SmoothRel=0xc,VLine=0x7,VLineRel=0x8\nc.RC:ALL=0x7,BASE_PATH=0x2,FINAL_PATH=0x1,TRANSFORM=0x4\nc.RQ:AUTO=0x0,BEST=0x4,CRISP=0x2,FAST=0x1,PRECISE=0x3\nc.TB:NOISE=0x1,TURBULENCE=0x0\nc.VBF:INCLUSIVE=0x1,NO_TRANSFORM=0x2\nc.VCLF:APPLY_FILLS=0x1,APPLY_STROKES=0x2\nc.VCS:INHERIT=0x0,LINEAR_RGB=0x2,SRGB=0x1\nc.VF:DISABLED=0x1,HAS_FOCUS=0x2,JOIN_PATHS=0x4\nc.VFA:MEET=0x0,NONE=0x1\nc.VFR:END=0x4,EVEN_ODD=0x2,INHERIT=0x3,NON_ZERO=0x1\nc.VGF:FIXED_CX=0x2000,FIXED_CY=0x4000,FIXED_FX=0x8000,FIXED_FY=0x10000,FIXED_RADIUS=0x20000,FIXED_X1=0x200,FIXED_X2=0x800,FIXED_Y1=0x400,FIXED_Y2=0x1000,SCALED_CX=0x10,SCALED_CY=0x20,SCALED_FX=0x40,SCALED_FY=0x80,SCALED_RADIUS=0x100,SCALED_X1=0x1,SCALED_X2=0x4,SCALED_Y1=0x2,SCALED_Y2=0x8\nc.VGT:CONIC=0x2,CONTOUR=0x4,DIAMOND=0x3,LINEAR=0x0,RADIAL=0x1\nc.VIJ:BEVEL=0x1,INHERIT=0x5,JAG=0x3,MITER=0x2,ROUND=0x4\nc.VIS:COLLAPSE=0x2,HIDDEN=0x0,INHERIT=0x3,VISIBLE=0x1\nc.VLC:BUTT=0x1,INHERIT=0x4,ROUND=0x3,SQUARE=0x2\nc.VLJ:BEVEL=0x3,INHERIT=0x5,MITER=0x0,MITER_REVERT=0x1,MITER_ROUND=0x4,ROUND=0x2\nc.VMF:AUTO_SPACING=0x2,STRETCH=0x1,X_MAX=0x10,X_MID=0x8,X_MIN=0x4,Y_MAX=0x80,Y_MID=0x40,Y_MIN=0x20\nc.VOF:HIDDEN=0x1,INHERIT=0x3,SCROLL=0x2,VISIBLE=0x0\nc.VPF:BITMAP_SIZED=0x1,OUTLINE_VIEWPORTS=0x8,RENDER_TIME=0x2,RESIZE=0x4\nc.VSM:AUTO=0x0,BESSEL=0x8,BICUBIC=0x3,BILINEAR=0x2,BLACKMAN3=0xc,BLACKMAN8=0xf,GAUSSIAN=0x7,KAISER=0x5,LANCZOS3=0xb,LANCZOS8=0xe,MITCHELL=0x9,NEIGHBOUR=0x1,QUADRIC=0x6,SINC3=0xa,SINC8=0xd,SPLINE16=0x4\nc.VSPREAD:CLIP=0x6,END=0x7,PAD=0x1,REFLECT=0x2,REFLECT_X=0x4,REFLECT_Y=0x5,REPEAT=0x3,UNDEFINED=0x0\nc.VTS:CONDENSED=0x6,EXPANDED=0x8,EXTRA_CONDENSED=0x5,EXTRA_EXPANDED=0xb,INHERIT=0x0,NARROWER=0x3,NORMAL=0x1,SEMI_CONDENSED=0x7,SEMI_EXPANDED=0x9,ULTRA_CONDENSED=0x4,ULTRA_EXPANDED=0xa,WIDER=0x2\nc.VTXF:AREA_SELECTED=0x20,BLINK=0x8,EDIT=0x10,EDITABLE=0x10,LINE_THROUGH=0x4,NO_SYS_KEYS=0x40,OVERLINE=0x2,OVERWRITE=0x80,RASTER=0x200,SECRET=0x100,UNDERLINE=0x1\nc.VUNIT:BOUNDING_BOX=0x1,END=0x3,UNDEFINED=0x0,USERSPACE=0x2\nc.WVC:BOTTOM=0x3,NONE=0x1,TOP=0x2\nc.WVS:ANGLED=0x2,CURVED=0x1,SAWTOOTH=0x3\n" +#define MOD_IDL "s.GradientStop:dOffset,eRGB:FRGB\ns.Transition:dOffset,sTransform\ns.VectorPoint:dX,dY,ucBit,ucBit\ns.VectorPainter:oPattern,oImage,oGradient,eColour:FRGB\ns.PathCommand:lType,ucLargeArc,ucSweep,ucPad1,dX,dY,dAbsX,dAbsY,dX2,dY2,dX3,dY3,dAngle\ns.VectorMatrix:pNext:VectorMatrix,oVector,dScaleX,dShearY,dShearX,dScaleY,dTranslateX,dTranslateY,lTag\ns.FontMetrics:lHeight,lLineSpacing,lAscent,lDescent\ns.MergeSource:lSourceType,oEffect\nc.ARC:LARGE=0x1,SWEEP=0x2\nc.ARF:MEET=0x40,NONE=0x100,SLICE=0x80,X_MAX=0x4,X_MID=0x2,X_MIN=0x1,Y_MAX=0x20,Y_MID=0x10,Y_MIN=0x8\nc.CM:BRIGHTNESS=0x6,COLOURISE=0x9,CONTRAST=0x5,DESATURATE=0x8,HUE=0x7,HUE_ROTATE=0x3,LUMINANCE_ALPHA=0x4,MATRIX=0x1,NONE=0x0,SATURATE=0x2\nc.CMP:ALL=0xffffffff,ALPHA=0x3,BLUE=0x2,GREEN=0x1,RED=0x0\nc.EM:DUPLICATE=0x1,NONE=0x3,WRAP=0x2\nc.FM:CHILD_HAS_FOCUS=0x4,HAS_FOCUS=0x2,LOST_FOCUS=0x8,PATH_CHANGED=0x1\nc.LS:DISTANT=0x0,POINT=0x2,SPOT=0x1\nc.LT:DIFFUSE=0x0,SPECULAR=0x1\nc.MOP:DILATE=0x1,ERODE=0x0\nc.OP:ARITHMETIC=0x5,ATOP=0x3,BURN=0xe,CONTRAST=0xc,DARKEN=0x9,DIFFERENCE=0x11,DODGE=0xd,EXCLUSION=0x12,HARD_LIGHT=0xf,IN=0x1,INVERT=0xb,INVERT_RGB=0xa,LIGHTEN=0x8,MINUS=0x14,MULTIPLY=0x7,OUT=0x2,OVER=0x0,OVERLAY=0x15,PLUS=0x13,SCREEN=0x6,SOFT_LIGHT=0x10,SUBTRACT=0x14,XOR=0x4\nc.PE:Arc=0x11,ArcRel=0x12,ClosePath=0x13,Curve=0x9,CurveRel=0xa,HLine=0x5,HLineRel=0x6,Line=0x3,LineRel=0x4,Move=0x1,MoveRel=0x2,QuadCurve=0xd,QuadCurveRel=0xe,QuadSmooth=0xf,QuadSmoothRel=0x10,Smooth=0xb,SmoothRel=0xc,VLine=0x7,VLineRel=0x8\nc.RC:ALL=0x7,BASE_PATH=0x2,FINAL_PATH=0x1,TRANSFORM=0x4\nc.RQ:AUTO=0x0,BEST=0x4,CRISP=0x2,FAST=0x1,PRECISE=0x3\nc.TB:NOISE=0x1,TURBULENCE=0x0\nc.VBF:INCLUSIVE=0x1,NO_TRANSFORM=0x2\nc.VCLF:APPLY_FILLS=0x1,APPLY_STROKES=0x2\nc.VCS:INHERIT=0x0,LINEAR_RGB=0x2,SRGB=0x1\nc.VF:DISABLED=0x1,HAS_FOCUS=0x2,JOIN_PATHS=0x4\nc.VFA:MEET=0x0,NONE=0x1\nc.VFR:END=0x4,EVEN_ODD=0x2,INHERIT=0x3,NON_ZERO=0x1\nc.VGF:FIXED_CX=0x2000,FIXED_CY=0x4000,FIXED_FX=0x8000,FIXED_FY=0x10000,FIXED_RADIUS=0x20000,FIXED_X1=0x200,FIXED_X2=0x800,FIXED_Y1=0x400,FIXED_Y2=0x1000,SCALED_CX=0x10,SCALED_CY=0x20,SCALED_FX=0x40,SCALED_FY=0x80,SCALED_RADIUS=0x100,SCALED_X1=0x1,SCALED_X2=0x4,SCALED_Y1=0x2,SCALED_Y2=0x8\nc.VGT:CONIC=0x2,CONTOUR=0x4,DIAMOND=0x3,LINEAR=0x0,RADIAL=0x1\nc.VIJ:BEVEL=0x1,INHERIT=0x5,JAG=0x3,MITER=0x2,ROUND=0x4\nc.VIS:COLLAPSE=0x2,HIDDEN=0x0,INHERIT=0x3,VISIBLE=0x1\nc.VLC:BUTT=0x1,INHERIT=0x4,ROUND=0x3,SQUARE=0x2\nc.VLJ:BEVEL=0x3,INHERIT=0x5,MITER=0x0,MITER_REVERT=0x1,MITER_ROUND=0x4,ROUND=0x2\nc.VMF:AUTO_SPACING=0x2,STRETCH=0x1,X_MAX=0x10,X_MID=0x8,X_MIN=0x4,Y_MAX=0x80,Y_MID=0x40,Y_MIN=0x20\nc.VOF:HIDDEN=0x1,INHERIT=0x3,SCROLL=0x2,VISIBLE=0x0\nc.VPF:BITMAP_SIZED=0x1,OUTLINE_VIEWPORTS=0x8,RENDER_TIME=0x2,RESIZE=0x4\nc.VSM:AUTO=0x0,BESSEL=0x8,BICUBIC=0x3,BILINEAR=0x2,BLACKMAN3=0xc,BLACKMAN8=0xf,GAUSSIAN=0x7,KAISER=0x5,LANCZOS3=0xb,LANCZOS8=0xe,MITCHELL=0x9,NEIGHBOUR=0x1,QUADRIC=0x6,SINC3=0xa,SINC8=0xd,SPLINE16=0x4\nc.VSPREAD:CLIP=0x6,END=0x7,PAD=0x1,REFLECT=0x2,REFLECT_X=0x4,REFLECT_Y=0x5,REPEAT=0x3,UNDEFINED=0x0\nc.VTS:CONDENSED=0x6,EXPANDED=0x8,EXTRA_CONDENSED=0x5,EXTRA_EXPANDED=0xb,INHERIT=0x0,NARROWER=0x3,NORMAL=0x1,SEMI_CONDENSED=0x7,SEMI_EXPANDED=0x9,ULTRA_CONDENSED=0x4,ULTRA_EXPANDED=0xa,WIDER=0x2\nc.VTXF:AREA_SELECTED=0x20,BLINK=0x8,EDIT=0x10,EDITABLE=0x10,LINE_THROUGH=0x4,NO_SYS_KEYS=0x40,OVERLINE=0x2,OVERWRITE=0x80,RASTER=0x200,SECRET=0x100,UNDERLINE=0x1\nc.VUNIT:BOUNDING_BOX=0x1,END=0x3,UNDEFINED=0x0,USERSPACE=0x2\nc.WVC:BOTTOM=0x3,NONE=0x1,TOP=0x2\nc.WVS:ANGLED=0x2,CURVED=0x1,SAWTOOTH=0x3\n" diff --git a/src/vector/scene/scene_clipping.cpp b/src/vector/scene/scene_clipping.cpp index 0cebf30e0..dd5320438 100644 --- a/src/vector/scene/scene_clipping.cpp +++ b/src/vector/scene/scene_clipping.cpp @@ -178,7 +178,7 @@ void SceneRenderer::ClipBuffer::draw(SceneRenderer &Scene) if (!m_clip->Viewport->Matrices) { VectorMatrix *matrix; - vecNewMatrix(m_clip->Viewport, &matrix); + vecNewMatrix(m_clip->Viewport, &matrix, false); } if (m_clip->Units IS VUNIT::BOUNDING_BOX) draw_bounding_box(Scene); @@ -191,7 +191,7 @@ void SceneRenderer::ClipBuffer::draw(SceneRenderer &Scene) void SceneRenderer::ClipBuffer::draw_userspace(SceneRenderer &Scene) { if (!m_clip->Viewport->Matrices) { - if (vecNewMatrix(m_clip->Viewport, NULL) != ERR::Okay) return; + if (vecNewMatrix(m_clip->Viewport, NULL, false) != ERR::Okay) return; } auto &matrix = m_clip->Viewport->Matrices; diff --git a/src/vector/scene/scene_draw.cpp b/src/vector/scene/scene_draw.cpp index 12cca5c27..14a2ad3f7 100644 --- a/src/vector/scene/scene_draw.cpp +++ b/src/vector/scene/scene_draw.cpp @@ -74,7 +74,7 @@ class SceneRenderer // inheritable values that arise as part of the drawing process (transformation management being an obvious example). // // NOTE: This feature is not intended to manage inheritable features that cross-over with SVG. For instance, fill -// values are not inheritable. Wherever it is possible to do so, inheritance should be managed by the client, with +// values are not inheritable. Wherever it is possible to do so, inheritance should be managed by the client, with // the goal of building a scene graph that has static properties. class VectorState { @@ -823,7 +823,7 @@ void SceneRenderer::draw_vectors(extVector *CurrentVector, VectorState &ParentSt // applied in realtime. if (!view->Fill[0].Pattern->Scene->Viewport->Matrices) { - vecNewMatrix(view->Fill[0].Pattern->Scene->Viewport, NULL); + vecNewMatrix(view->Fill[0].Pattern->Scene->Viewport, NULL, false); } // Use transforms for the purpose of placing the pattern correctly @@ -859,7 +859,7 @@ void SceneRenderer::draw_vectors(extVector *CurrentVector, VectorState &ParentSt if ((view->FGFill) and (view->Fill[1].Pattern)) { // Support for foreground fill patterns if (!view->Fill[1].Pattern->Scene->Viewport->Matrices) { - vecNewMatrix(view->Fill[1].Pattern->Scene->Viewport, NULL); + vecNewMatrix(view->Fill[1].Pattern->Scene->Viewport, NULL, false); } auto &matrix = view->Fill[1].Pattern->Scene->Viewport->Matrices; diff --git a/src/vector/vector.fdl b/src/vector/vector.fdl index 9e01c41dc..26457ad6a 100644 --- a/src/vector/vector.fdl +++ b/src/vector/vector.fdl @@ -346,8 +346,18 @@ module({ name="Vector", copyright="Paul Manias © 2010-2024", version=1.0 }, fun double ScaleY # Matrix value D double TranslateX # Matrix value E double TranslateY # Matrix value F + int Tag # An optional tag value defined by the client for matrix identification. ]]) + // Pre-defined tags for VectorMatrix + + hash("MTAG", "0x%s", + "ANIMATE-MOTION", + "ANIMATE-TRANSFORM", + "SCENE-GRAPH", + "USE-TRANSFORM", + "SVG-TRANSFORM") + struct("FontMetrics", { comment="Font metrics, measured in pixels relative to the display" }, [[ int Height # Capitalised font height int LineSpacing # Vertical advance from one line to the next diff --git a/src/vector/vectors/vector.cpp b/src/vector/vectors/vector.cpp index c7ee41912..79c12f707 100644 --- a/src/vector/vectors/vector.cpp +++ b/src/vector/vectors/vector.cpp @@ -621,6 +621,7 @@ transform is no longer required before then, it can be manually removed with ~Ve -INPUT- &resource(*VectorMatrix) Transform: A reference to the new transform structure is returned here. +int End: If true, the matrix priority is lowered by inserting it at the end of the transform list. -ERRORS- Okay: @@ -634,10 +635,8 @@ static ERR VECTOR_NewMatrix(extVector *Self, struct vecNewMatrix *Args) VectorMatrix *transform; if (AllocMemory(sizeof(VectorMatrix), MEM::DATA|MEM::NO_CLEAR, &transform) IS ERR::Okay) { - // Insert transform at the start of the list. transform->Vector = Self; - transform->Next = Self->Matrices; transform->ScaleX = 1.0; transform->ScaleY = 1.0; transform->ShearX = 0; @@ -645,7 +644,17 @@ static ERR VECTOR_NewMatrix(extVector *Self, struct vecNewMatrix *Args) transform->TranslateX = 0; transform->TranslateY = 0; - Self->Matrices = transform; + if ((Args->End) and (Self->Matrices)) { + transform->Next = NULL; + VectorMatrix *last = Self->Matrices; + while (last->Next) last = last->Next; + last->Next = transform; + } + else { // Insert transform at the start of the list. + transform->Next = Self->Matrices; + Self->Matrices = transform; + } + Args->Transform = transform; mark_dirty(Self, RC::TRANSFORM); @@ -981,7 +990,7 @@ static ERR VECTOR_SubscribeKeyboard(extVector *Self, struct vecSubscribeKeyboard Trace: Returns the coordinates for a vector path, using callbacks. Any vector that generates a path can be traced by calling this method. Tracing allows the caller to follow the path -from point-to-point if the path were to be rendered with a stroke. The prototype of the callback function is +from point-to-point if the path were to be rendered with a stroke. The prototype of the callback function is `ERR Function(OBJECTPTR Vector, LONG Index, LONG Command, DOUBLE X, DOUBLE Y, APTR Meta)`. The Vector parameter refers to the vector targeted by the method. The Index is an incrementing counter that reflects diff --git a/src/vector/vectors/vector_def.c b/src/vector/vectors/vector_def.c index 0037a637a..6eec39d32 100644 --- a/src/vector/vectors/vector_def.c +++ b/src/vector/vectors/vector_def.c @@ -67,7 +67,7 @@ FDEF maPointInPath[] = { { "X", FD_DOUBLE }, { "Y", FD_DOUBLE }, { 0, 0 } }; FDEF maSubscribeInput[] = { { "Mask", FD_LONG }, { "Callback", FD_FUNCTIONPTR }, { 0, 0 } }; FDEF maSubscribeKeyboard[] = { { "Callback", FD_FUNCTIONPTR }, { 0, 0 } }; FDEF maSubscribeFeedback[] = { { "Mask", FD_LONG }, { "Callback", FD_FUNCTIONPTR }, { 0, 0 } }; -FDEF maNewMatrix[] = { { "VectorMatrix:Transform", FD_PTR|FD_STRUCT|FD_RESOURCE|FD_RESULT }, { 0, 0 } }; +FDEF maNewMatrix[] = { { "VectorMatrix:Transform", FD_PTR|FD_STRUCT|FD_RESOURCE|FD_RESULT }, { "End", FD_LONG }, { 0, 0 } }; FDEF maFreeMatrix[] = { { "VectorMatrix:Matrix", FD_PTR|FD_STRUCT }, { 0, 0 } }; static const struct MethodEntry clVectorMethods[] = {