From 5b53b71f4eb7b39c6474c6385e301cca57920771 Mon Sep 17 00:00:00 2001 From: Jaeun Choi Date: Wed, 29 Aug 2018 23:24:45 +0900 Subject: [PATCH] lottie/example: enhanced lottieviewer example Change-Id: I0eb2a8c528376397190c75b084e9510c3698c034 --- example/lottieview.cpp | 77 ++++++++++++--- example/lottieview.h | 7 +- example/lottieviewer.cpp | 230 +++++++++++++++++++++++++++++++++++++------- example/resource/layout.edc | 31 +++++- example/resource/layout.edj | Bin 8870 -> 9149 bytes 5 files changed, 294 insertions(+), 51 deletions(-) diff --git a/example/lottieview.cpp b/example/lottieview.cpp index 79ce8fb..82eba73 100644 --- a/example/lottieview.cpp +++ b/example/lottieview.cpp @@ -6,8 +6,12 @@ static Eina_Bool animator(void *data , double pos) { LottieView *view = static_cast(data); - view->seek(pos); - if (pos == 1.0) { + float nextPos = pos + view->mStartPos; + if (nextPos > 1.0) nextPos = 1.0; + + view->seek(nextPos); + if (nextPos == 1.0) { + view->mAnimator = NULL; view->finished(); return EINA_FALSE; } @@ -81,6 +85,38 @@ void LottieView::update(const std::vector &renderList) evas_object_vg_root_node_set(mVg, root); } +static void mImageDelCb(void *data, Evas *evas, Evas_Object *obj, void *) +{ + LottieView *lottie = (LottieView *)data; + + if (lottie->mImage != obj) return; + + lottie->mImage = NULL; + lottie->stop(); +} + +static void mVgDelCb(void *data, Evas *evas, Evas_Object *obj, void *) +{ + LottieView *lottie = (LottieView *)data; + if (lottie->mVg != obj) return; + + lottie->mVg = NULL; + lottie->stop(); +} + +void LottieView::initializeBufferObject(Evas *evas) +{ + if (mRenderMode) { + mImage = evas_object_image_filled_add(evas); + evas_object_image_colorspace_set(mImage, EVAS_COLORSPACE_ARGB8888); + evas_object_image_alpha_set(mImage, EINA_TRUE); + evas_object_event_callback_add(mImage, EVAS_CALLBACK_DEL, mImageDelCb, this); + } else { + mVg = evas_object_vg_add(evas); + evas_object_event_callback_add(mVg, EVAS_CALLBACK_DEL, mVgDelCb, this); + } +} + LottieView::LottieView(Evas *evas, bool renderMode, bool asyncRender):mVg(nullptr), mImage(nullptr) { mPalying = false; @@ -95,13 +131,7 @@ LottieView::LottieView(Evas *evas, bool renderMode, bool asyncRender):mVg(nullpt mRenderMode = renderMode; mAsyncRender = asyncRender; - if (mRenderMode) { - mImage = evas_object_image_filled_add(evas); - evas_object_image_colorspace_set(mImage, EVAS_COLORSPACE_ARGB8888); - evas_object_image_alpha_set(mImage, EINA_TRUE); - } else { - mVg = evas_object_vg_add(evas); - } + initializeBufferObject(evas); } LottieView::~LottieView() @@ -115,6 +145,14 @@ LottieView::~LottieView() delete mPlayer; } +Evas_Object *LottieView::getImage() { + if (mRenderMode) { + return mImage; + } else { + return mVg; + } +} + void LottieView::show() { if (mRenderMode) { @@ -139,16 +177,17 @@ void LottieView::seek(float pos) if (mPalying && mReverse) pos = 1.0 - pos; + mPos = pos; + if (mRenderMode) { LOTBuffer buf; evas_object_image_size_get(mImage, &buf.width, &buf.height); if (mAsyncRender) { if (mRenderTask.valid()) return; mDirty = true; - mPendingPos = pos; buf.buffer = (uint32_t *)evas_object_image_data_get(mImage, EINA_TRUE); buf.bytesPerLine = evas_object_image_stride_get(mImage); - mRenderTask = mPlayer->render(mPendingPos, buf); + mRenderTask = mPlayer->render(mPos, buf); mBuffer = buf; // to force a redraw evas_object_image_data_update_add(mImage, 0 , 0, buf.width, buf.height); @@ -168,6 +207,11 @@ void LottieView::seek(float pos) } } +float LottieView::getPos() +{ + return mPos; +} + void LottieView::render() { if (!mDirty) return; @@ -186,7 +230,7 @@ void LottieView::render() } mBuffer.buffer = nullptr; } else { - const std::vector &renderList = mPlayer->renderList(mPendingPos); + const std::vector &renderList = mPlayer->renderList(mPos); update(renderList); } } @@ -239,6 +283,8 @@ void LottieView::setRepeatMode(LottieView::RepeatMode mode) void LottieView::play() { + mStartPos = mPos; + if (mAnimator) ecore_animator_del(mAnimator); mAnimator = ecore_animator_timeline_add(mPlayer->playTime()/mSpeed, animator, this); mReverse = false; mCurCount = mRepeatCount; @@ -253,6 +299,10 @@ void LottieView::pause() void LottieView::stop() { mPalying = false; + if (mAnimator) { + ecore_animator_del(mAnimator); + mAnimator = NULL; + } } void LottieView::restart() @@ -263,6 +313,9 @@ void LottieView::restart() mReverse = !mReverse; else mReverse = false; + + mStartPos = 0; + if (mAnimator) ecore_animator_del(mAnimator); mAnimator = ecore_animator_timeline_add(mPlayer->playTime()/mSpeed, animator, this); } } diff --git a/example/lottieview.h b/example/lottieview.h index e3e02d5..c94247c 100644 --- a/example/lottieview.h +++ b/example/lottieview.h @@ -25,7 +25,7 @@ public: }; LottieView(Evas *evas, bool renderMode = true, bool asyncRender = true); ~LottieView(); - Evas_Object * getImage() { return mRenderMode ? mImage : mVg; } + Evas_Object *getImage(); void setSize(int w, int h); void setPos(int x, int y); void setFilePath(const char *filePath); @@ -39,11 +39,13 @@ public: long getTotalFrame() const { return mTotalFrame; } public: void seek(float pos); + float getPos(); void finished(); void play(); void pause(); void stop(); void render(); + void initializeBufferObject(Evas *evas); private: void createVgNode(LOTNode *node, Efl_VG *parent); void update(const std::vector &); @@ -67,7 +69,8 @@ public: bool mRenderMode; bool mAsyncRender; bool mDirty; - float mPendingPos; + float mStartPos; + float mPos; float mFrameRate; long mTotalFrame; std::future mRenderTask; diff --git a/example/lottieviewer.cpp b/example/lottieviewer.cpp index 9e80c55..1d0b2e3 100644 --- a/example/lottieviewer.cpp +++ b/example/lottieviewer.cpp @@ -2,6 +2,10 @@ #include "lottieview.h" #include #include +#include +#include +#include +#include using namespace std; @@ -9,13 +13,74 @@ typedef struct _AppInfo AppInfo; struct _AppInfo { LottieView *view; Evas_Object *layout; + Evas_Object *slider; + Evas_Object *button; + Ecore_Evas *ee; + Eina_Bool autoPlaying; }; +typedef struct _ItemData ItemData; +struct _ItemData { + int index; +}; + +Eina_List *jsonFiles; +bool renderMode = true; + +static void +_layout_del_cb(void *data, Evas *, Evas_Object *, void *) +{ + AppInfo *info = (AppInfo *)data; + if (info->view) delete info->view; + info->view = NULL; + + ecore_evas_data_set(info->ee, "AppInfo", NULL); + + free(info); +} + +static void +_update_frame_info(AppInfo *info, double pos) +{ + int frameNo = pos * info->view->getTotalFrame(); + char buf[64]; + + sprintf(buf, "%d / %ld", frameNo, info->view->getTotalFrame()); + elm_object_part_text_set(info->layout, "text", buf); +} + +static void +_toggle_start_button(AppInfo *info) +{ + if (!info->autoPlaying) + { + info->autoPlaying = EINA_TRUE; + info->view->play(); + elm_object_text_set(info->button, "Stop"); + } + else + { + info->autoPlaying = EINA_FALSE; + info->view->stop(); + elm_object_text_set(info->button, "Start"); + } +} + static void -_win_del_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) +_ee_pre_render_cb(Ecore_Evas *ee) { - LottieView *view = (LottieView *)data; - delete view; + AppInfo *info = (AppInfo *)ecore_evas_data_get(ee, "AppInfo"); + + if (info && info->autoPlaying && info->view) + { + float pos = info->view->getPos(); + _update_frame_info(info, pos); + elm_slider_value_set(info->slider, (double)pos); + info->view->render(); + + if (pos >= 1.0) + _toggle_start_button(info); + } } static void @@ -24,37 +89,34 @@ _slider_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) double val = elm_slider_value_get(obj); AppInfo *info = (AppInfo *)data; - int frameNo = val * info->view->getTotalFrame(); - char buf[64]; + _update_frame_info(info, val); - sprintf(buf, "%d / %ld", frameNo, info->view->getTotalFrame()); + if (!info->autoPlaying) + { + info->view->seek(val); + info->view->render(); + } +} - elm_object_part_text_set(info->layout, "text", buf); +static void +_button_clicked_cb(void *data, Evas_Object *obj, void *event_info) +{ + AppInfo *info = (AppInfo *)data; - info->view->seek(val); - info->view->render(); + _toggle_start_button(info); } -EAPI_MAIN int -elm_main(int argc EINA_UNUSED, char **argv EINA_UNUSED) +Evas_Object * +create_layout(Evas_Object *parent, char *file) { - Evas_Object *win, *layout, *slider, *image; - bool renderMode = true; + Evas_Object *layout, *slider, *image, *button; + Evas *e; + Ecore_Evas *ee; char buf[64]; - AppInfo appInfo; - - if (argc > 1) { - if (!strcmp(argv[1], "--disable-render")) - renderMode = false; - } - - elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); - win = elm_win_util_standard_add("lottie", "LottieViewer"); - elm_win_autodel_set(win, EINA_TRUE); - evas_object_resize(win, 500, 700); - evas_object_show(win); + AppInfo *info = (AppInfo *)calloc(sizeof(AppInfo), 1); - layout = elm_layout_add(win); + //LAYOUT + layout = elm_layout_add(parent); evas_object_show(layout); evas_object_size_hint_weight_set(layout, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); @@ -62,29 +124,42 @@ elm_main(int argc EINA_UNUSED, char **argv EINA_UNUSED) edjPath +="layout.edj"; elm_layout_file_set(layout, edjPath.c_str(), "layout"); - elm_win_resize_object_add(win, layout); + //LOTTIEVIEW + LottieView *view = new LottieView(evas_object_evas_get(layout), renderMode); std::string filePath = DEMO_DIR; - filePath +="insta_camera.json"; + filePath +=file; - LottieView *view = new LottieView(evas_object_evas_get(win), renderMode); view->setFilePath(filePath.c_str()); view->setSize(500, 500); - evas_object_smart_callback_add(win, "delete,request", _win_del_cb, (void *)view); - + //IMAGE from LOTTIEVIEW image = view->getImage(); evas_object_show(image); evas_object_size_hint_min_set(image, 500, 500); elm_object_part_content_set(layout, "lottie", image); - appInfo.view = view; - appInfo.layout = layout; - + //SLIDER slider = elm_slider_add(layout); - evas_object_show(slider); elm_object_part_content_set(layout, "slider", slider); - evas_object_smart_callback_add(slider, "changed", _slider_cb, (void *)&appInfo); + evas_object_smart_callback_add(slider, "changed", _slider_cb, (void *)info); + + button = elm_button_add(layout); + elm_object_text_set(button, "Start"); + elm_object_part_content_set(layout, "button", button); + evas_object_smart_callback_add(button, "clicked", _button_clicked_cb, (void *)info); + + e = evas_object_evas_get(layout); + ee = ecore_evas_ecore_evas_get(e); + ecore_evas_data_set(ee, "AppInfo", info); + ecore_evas_callback_pre_render_set(ee, _ee_pre_render_cb); + + info->view = view; + info->layout = layout; + info->slider = slider; + info->button = button; + info->ee = ee; + evas_object_event_callback_add(layout, EVAS_CALLBACK_DEL, _layout_del_cb, (void *)info); sprintf(buf, "%d / %ld", 0, view->getTotalFrame()); elm_object_part_text_set(layout, "text", buf); @@ -92,6 +167,89 @@ elm_main(int argc EINA_UNUSED, char **argv EINA_UNUSED) view->seek(0.0); view->render(); + return layout; +} + +static void +_gl_selected_cb(void *data, Evas_Object *obj, void *event_info) +{ + Evas_Object *nf = (Evas_Object *)data; + Elm_Object_Item *it = (Elm_Object_Item *)event_info; + elm_genlist_item_selected_set(it, EINA_FALSE); + + Evas_Object *layout = create_layout(nf, (char *)eina_list_nth(jsonFiles, (elm_genlist_item_index_get(it) - 1))); + elm_naviframe_item_push(nf, NULL, NULL, NULL, layout, NULL); +} + +static char * +_gl_text_get(void *data, Evas_Object *obj, const char *part) +{ + ItemData *id = (ItemData *) data; + char *str = (char *)eina_list_nth(jsonFiles, id->index); + return strdup(str); +} + +static void +_gl_del(void *data, Evas_Object *obj) +{ +} + +EAPI_MAIN int +elm_main(int argc EINA_UNUSED, char **argv EINA_UNUSED) +{ + Evas_Object *win, *nf, *genlist; + Elm_Genlist_Item_Class *itc = elm_genlist_item_class_new(); + ItemData *itemData; + DIR *dir; + struct dirent *ent; + int i, fileCount = 0; + + if (argc > 1) { + if (!strcmp(argv[1], "--disable-render")) + renderMode = false; + } + + //WIN + elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); + win = elm_win_util_standard_add("lottie", "LottieViewer"); + elm_win_autodel_set(win, EINA_TRUE); + evas_object_resize(win, 500, 700); + evas_object_show(win); + + //NAVIFRAME + nf = elm_naviframe_add(win); + evas_object_size_hint_weight_set(nf, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_win_resize_object_add(win, nf); + evas_object_show(nf); + + //GENLIST + genlist = elm_genlist_add(nf); + elm_genlist_mode_set(genlist, ELM_LIST_COMPRESS); + evas_object_smart_callback_add(genlist, "selected", _gl_selected_cb, nf); + + itc->item_style = "default"; + itc->func.text_get = _gl_text_get; + itc->func.del = _gl_del; + + std::string rscPath = DEMO_DIR; + + dir = opendir(rscPath.c_str()); + while ((ent = readdir(dir)) != NULL) { + if (!strncmp(ent->d_name + (strlen(ent->d_name) - 4), "json", 4)) { + jsonFiles = eina_list_append(jsonFiles, strdup(ent->d_name)); + fileCount++; + } + } + closedir(dir); + + for (i = 0; i < fileCount; i++) { + itemData = (ItemData *)calloc(sizeof(ItemData), 1); + itemData->index = i; + elm_genlist_item_append(genlist, itc, (void *)itemData, NULL, ELM_GENLIST_ITEM_NONE, NULL, NULL); + } + + elm_naviframe_item_push(nf, "Lottie Viewer", NULL, NULL, genlist, NULL); + elm_run(); return 0; diff --git a/example/resource/layout.edc b/example/resource/layout.edc index f13e870..9754416 100644 --- a/example/resource/layout.edc +++ b/example/resource/layout.edc @@ -45,22 +45,51 @@ collections { align: 0.5 1.0; rel1.to_x: "left_pad"; rel1.relative: 1.0 1.0; - rel2.to_x: "right_pad"; + rel2.to_x: "button_left_pad"; rel2.relative: 0.0 1.0; + fixed: 0 1; } } spacer { "left_pad"; desc { state: "default" 0.0; + fixed: 1 0; min: 20 0; + align: 0.0 0.5; rel2.relative: 0.0 0.0; } } spacer { "right_pad"; desc { state: "default" 0.0; + fixed: 1 0; min: 20 0; + align: 1.0 0.5; rel1.relative: 1.0 0.0; } } + swallow { "button"; + desc { state: "default" 0.0; + fixed: 1 1; + min: 80 55; + align: 1.0 0.5; + rel1.to_x: "right_pad"; + rel1.to_y: "slider"; + rel1.relative: 0.0 0.5; + rel2.to_x: "right_pad"; + rel2.to_y: "slider"; + rel2.relative: 0.0 0.5; + } + } + spacer { "button_left_pad"; + desc { state: "default" 0.0; + fixed: 1 0; + min: 20 0; + align: 1.0 0.5; + rel1.to_x: "button"; + rel1.relative: 0.0 0.0; + rel2.to_x: "button"; + rel2.relative: 0.0 1.0; + } + } } } } diff --git a/example/resource/layout.edj b/example/resource/layout.edj index 38add6be83136ffc2092b212719c747638201afc..8a74b1a345c76154f724cb537a3974272c7a2e73 100644 GIT binary patch literal 9149 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 PpXNHJzn#|QdXD=qmaHS* literal 8870 zcmZ{n34Bav_s6d##1dqer6DOz5Q9W)MP*MUku=f}%D9=ilUycq$K07D6IHQ`G*qu$kljv6Fd)kTbZ9^ zm=@O=rhgoG0hqguGq=%S2))%y`GI~8>_f;FXbiy+J;0ViI#H||Yz6vlsc$EOeG2A6 zv3X!Sz?>=O5B4QkJBkehI|9}kO_?_otP0)slwB>@1u$pI4!&`{hCaQPyI{Y8wW93W zfYqVjh8k}<*b^{%eb}q(3kL?DyOFV)qbcjNf{}I5tO$(won~KwwFjg3(E!#N3~REF zhqG`CM3>$h*5=m3f$aqA2S&zJ)-oLoK5XK@GO!phdcV8DQV2s+zWoa<6J0tEnt{Db z7-i=JmW@8`=c!Xv%)$!Hy9|+06v|39JKUw*>5#14CZ8!Cz8tDdRN<`^SO3jx-~A zOwSt(Msk>*_a<03FnY}oz`_VaQ|8csjX;<7eKy!g!q61Ej$j$+(!M$jrUs+q>MmFg zVZ;Z-9_=}p;9y7A0>9erRM1a?&48q12!3*3Kp5rIJ7Dl(6TAChs~qe;1zQj1f~M?a z9N136sPT|*?$`_CM(pqq-);jt3rYLnbFj;VQQx8lxz{={oVj})m=u4II3H#jSUn`| zzh+>>N75XSerP|S9FzUi>qGu|cqt6BH5%6Efmo3I3&FsK=?zBwNYAkWY%mx-UKLmz z7@g-|fTe@EqA9+@JRXRTCjR>p%m_x}O|e6sddxxBmGVmk*vDYrlpW5^13qL(eIm~w zv`DaHko1|8dC!1(qLGKhBG%@CSYSM+u`|)|pz%J}?sF^mxy}iXB)u7>Qk9>f11|tq!ao*m1(p zl)WKVJa0JI;Xd(faA3$cFL#AO_D6$_7vh6?4UIhHJ^b!Pd`s8o@nC6SEvWU)1se@U za#>l+Hn554N;L;E77a1vWq_1o2$J{>ce~dNNc!8&U<<+M^FUmAk^GhX2bqM181N!~ zk>Y@?2l{-lZy>#>_2DeMeg<==814kGyI@Z8cO9YgVD};ETzCS8bF$|unF~JhZth@* zT=OPlNc({7h4v8`IiC(_ieHMrLeM2KNFL$~#IrZz(w3Ky#J`Ap?-7vFJqVeK248uT zyhI#IPY(W?z{W!QQG4qKCW6uXI0se?CgnaPY6~+0tQ3;|eNYFs+=0Qz-s{2WxOWD_ z88^B2TY&8WlY9@^18p+c_mFgbCcdf$lg5LbiuM@n8YITG`wB7XeH&e6zbtf|x%X2@ zsWw4keC7aB4RbfS&oN%-wuGUPXC~ToFye3J?(v463FZR{-`eNh1xD%tozt~op$da+ zgSH!N2*t=av%wM+CdbwtFwA4iBgj!`|!el#NuzIkLq)#5`-5JJ*oSU>anc<9m$hpwI9}Wgv>Hbsl2>$dL0jAuc z^PxWki-)9R?IakA^TB$FbjG<*aE`Pl(it% ze3lX>?_n->+krc!JObSoH%dnpGY zhoUtDBj={ffjJr2`XQ$6>W=(IRV5%BKj9EROV^9^!(#jO#;ER}8vi|TGbtb2=y-|m2 z86989X;lTX%w_U;L!L#i%4PF8jMtBhCZhsHk&N&OxXPy6rq5#w3B_Mf{IyZh%o(v1 zNmv!&tkXt~CQgUVm^ghu75=bhKA%$;I`pl!zM#{IoLPA*_vClHR^iYj z7UnP>)tBGXviGZYk|r1=A?68!nH+;r#p;c@ zwppYp)Uri3tC^y)==G9Kks##BgG#EEk!)2(Mm=lb)TAtXqG*#|J^BBXK!y|xME@{t!JL1wEFacVn1b+(|jGMr9_Qp4m4 z7Lili3>ns7=GCZ+L&jwF z2qOauwR9V(^EfjC95Ep9Bv;jW0_?Cnlhvk*9Jvi_y2fG>1=$4U*36S&W>}4e1RWyJ z%pv7-dA%0(T`f5X>z!)hO~^N?jFVGRW_m@`X|}l;yQ>ZdkE;?xpTStC_nrXYLZG)mqY??4mk3uf;BIQ3i z?lXvoL_JO$&f|1Qa8}EhNMV*V5hWQv2e4^X0k1_(pg=As)o-IoFp{edx1wD|?U1N+ zSK8HFM$cLWi8bCe$$C`kSj8$b%02~V zh)o|AnJm8=ofa7#o33<5MW&5TNL7}Q5<5CUJt{RuSw?bVe1h^WIx;0TJyQNI6Wd5t z1|hc>lVcJrM!7WFD-3GC;stdKijT%Dpqj;!nr2IyIC4=BmQR1ZcA?9J&q@<6*E1gnwQm2#FO#&Nyulx<{AcjQ6Cd_Z>DO#k zhp-QZo?UFMtvs=mZ#3T>v~E%D2f@|#u8oVYEbj8nj2+LnPWgM{H=WF{FE2WFYtyQ) znw|7Zj!$>?;#Iqw=S`TCw&j%aBunw_gAeQAfS**E%I>TlrWsruJO*CU>Ch>sk43m$Ku12D;wf zsoT5uqrj`W)u$I~MJ(uYM``{4-;pdaH1E+4e_H4W~+m#&%2G zUF0&Q%j2JBK4qM~i}vtc{9$;&(%_KH`PWDPlvZ#kXVWvwsm+yVS6_PTbitXIzYTx7 zH+J1R@A~Vr4sCeU>!I&E9}Mh&qrBjkVGSdOpPw=GTw}7T%KPER??o>evL*ZQvzva? z-3Q@cM`?X^tK!q#-}awBa6@3rUcQx+VqN{3=f`@qTX^`X@7v#AKe);H$@OV}PKaL| zaA;8N2V>4I^6vQV(4vLHz}1J=ea$xS=N??rCtN7(^6bisf~V7;<$t+z)1{^BDtI9x zrB`TARpL+Yy;V`=GpbAJq2QEXo~~^0SaM^^uf2oR_fG}id+Z*w>+gLTIja*gm)7NX zx%c?}lG8z(e(O|qUo-!Q5|h8z^Ouk7^PL7+63$Ob+STepdW@;r#pUm2c6~9R-$Czb z+al(fTsBRNz7au0;WvU^+DH#M<*>5 zhd=046_?|5=~3FDsCj`N*_Ag7C&j1L#HZK9pPL!#c6a_e0|xl|`9+s`R^K^sJ|be# z-o?RdL(aFXsR~M&HEiXqHK`lFug*>VthC-e>aC-PiW|$$94kI^r1(tSirL414LW@^ z`;n_x*U106 zPAwR7erG0|oLReeN95Sj<39&}e&YJTA9DwsZ`{|04L{;FXPfiYPJ3VfW5lJBo$c6x zM*?~$XWb9Z$_&Z+Ze8d#_WSc~>n?Ol%2}~%kJHLrQ}TKz?cLa{qPLe#?4H#9RKoYW zf)<4KuI!PtyqvqnS&Ow6$Jh-f4!-XHMpY5M=b7=S5bJY?1+h6VYxCiexZ&>R<@4$|cB`GDn z)3d{Cx*7)0F?PPWAmR3l#JUv;@$893)qz7I-#dS3-IIa&ZO0EUGq$=JdSvjNN1dOv zGoS8rzW3ekLu&g}`^<<9*jxPj)>)gb`>x)Sns)EjfgPngVlN)J^xF7)*{LD-vP1nZ zTPoi?J^y6>IPXV?=Y*dgxV^(T?~6~qduLneg}UfHJ4*8=Pw;=b>(KV=`{s2$mGRh` zp0ca`)3UAQ8_T)Rzn=QV)`8on_BrJ@a@m96q%XbHHy-vRZaEnG+Q?;tGT5!(1+_G9Pc^)VYxr2XCFaeeZ-+r7)zRX_0# bnm)6-ru(ql@68?dbzI^4qrz&v_Wl0@ZYZFE -- 2.7.4