import math as _m, random as _r _FLOAT=0; _GATHER=1; _LINES=2; _LFADE=3; _EXPAND=4; _ELLIPSE=5 _CHAR_W_LATIN=10.0; _CHAR_W_CJK=18.0; _SLOTS_PER_LBL=20 def _cjk(ch): return '\u4e00'<=ch<='\u9fff' def _tier(): x=_r.random() return 0 if x<0.3 else (1 if x<0.7 else 2) def _spd(t,s1,s2,s3): if t==0: return _r.uniform(s1*.8,s1*1.2) if t==1: return _r.uniform(s2*.8,s2*1.2) return _r.uniform(s3*.8,s3*1.2) def _dir(): return -1 if _r.random()>0.1 else 1 def _cpos(i,W,H): a=i/18*2*_m.pi-_m.pi/2 r=min(W,H)*.28 return W/2+r*_m.cos(a), H/2+r*_m.sin(a) def _epos(i,W,H): a=i/18*2*_m.pi-_m.pi/2 return W/2+W*.47*_m.cos(a), H/2+H*.43*_m.sin(a) def _ease(t): t=max(0.,min(1.,t)) return 2*t*t if t<.5 else -1+(4-2*t)*t def _init_bg(W,H,s1,s2,s3): out=[] for i in range(80): t=_tier() out.append({'x':_r.uniform(0,W),'y':i/80*H,'speed':_spd(t,s1,s2,s3)*_dir()}) return out def _init_cd(W,H,s1,s2,s3,cw): out=[] for i,w in enumerate(cw): t=_tier() out.append({'x':_r.uniform(0,W),'y':_r.uniform(0,H),'speed':_spd(t,s1,s2,s3)*_dir(),'idx':i,'word':w}) return out def _build_rows(nr,rs,wpr,W,H,aw,s1,s2,s3): fs=max(12,min(80,int(W*.85/max(1,wpr*12)/(10/18)*18))) cw=fs*.6; out=[] for i in range(nr): t=_tier(); sp=_spd(t,s1,s2,s3)*_dir() sel=_r.choices(aw,k=wpr) if aw else [] gaps=[_r.uniform(60,140) for _ in range(max(0,wpr-1))] wws=[max(40,len(w.get('english',''))*cw) for w in sel] tw=sum(wws)+sum(gaps) sx=_r.uniform(-tw*.5,W-tw*.3) wr=[]; x=sx for j,w in enumerate(sel): wr.append({'id':w['id'],'text':w['english'],'x':x,'width':wws[j]}) x+=wws[j]+(gaps[j] if j.001 or pts!=ts_div: bg=_init_bg(W,H,s1,s2,s3) cd=_init_cd(W,H,s1,s2,s3,cw) rows=_build_rows(nr,rs2,wpr,W,H,aw,s1,s2,s3) root.store('prev_wpr',wpr); root.store('prev_s1',s1); root.store('prev_ts',ts_div); root.store('font_size',fs) fi_spd=root.par.Fadein.val; fo_spd=root.par.Fadeoutspd.val ta=max(0.,ta-fo_spd) if ast!=_FLOAT else min(1.,ta+fi_spd) for row in rows: for wd in row['words']: wd['x']+=row['speed']*dt if row['words']: lft=min(wd['x'] for wd in row['words']) rgt=max(wd['x']+wd['width'] for wd in row['words']) sh=W+row['total_w']+160 if row['speed']<0 and rgt<-80: for wd in row['words']: wd['x']+=sh elif row['speed']>0 and lft>W+80: for wd in row['words']: wd['x']-=sh for d in bg: d['x']+=d['speed']*dt if d['speed']<0 and d['x']<-80: d['x']+=W+160 elif d['speed']>0 and d['x']>W+80: d['x']-=W+160 for i,d in enumerate(cd): if ast==_FLOAT: d['x']+=d['speed']*dt if d['speed']<0 and d['x']<-80: d['x']+=W+160 elif d['speed']>0 and d['x']>W+80: d['x']-=W+160 elif ast in (_GATHER,_LINES,_LFADE): tx,ty=_cpos(i,W,H) d['x']+=(tx-d['x'])*.04*dt; d['y']+=(ty-d['y'])*.04*dt elif ast==_EXPAND: tx,ty=_cpos(i,W,H); ex,ey=_epos(i,W,H); t=_ease(et) d['x']=tx+(ex-tx)*t; d['y']=ty+(ey-ty)*t elif ast==_ELLIPSE: ex,ey=_epos(i,W,H); d['x']=ex; d['y']=ey if ast==_GATHER: gt-=dt/60. if gt<=0: p=sorted(_r.sample(range(18),4)); pid0,pid1,pid2,pid3=p; pids=p segs=[{'from_idx':p[k],'to_idx':p[k+1],'progress':0.,'done':False} for k in range(3)] las=[0.,0.,0.,0.]; cs=0; ast=_LINES elif ast==_LINES: if cs=1. and not segs[cs]['done']: segs[cs]['done']=True; cs+=1 las[0]=min(1.,las[0]+.02*dt) if len(segs)>0 and segs[0]['done']: las[1]=min(1.,las[1]+.02*dt) if len(segs)>1 and segs[1]['done']: las[2]=min(1.,las[2]+.02*dt) if len(segs)>2 and segs[2]['done']: las[3]=min(1.,las[3]+.02*dt) if all(s['done'] for s in segs) and all(a>=1. for a in las) and pt<0: pt=lp if pt>=0: pt-=dt/60. if pt<=0: pt=-1; ast=_LFADE; lfp=0. elif ast==_LFADE: lfp=min(1.,lfp+.015*dt) if lfp>=1.: ast=_EXPAND; et=0. elif ast==_EXPAND: et=min(1.,et+es*dt) if et>=1.: ast=_ELLIPSE elif ast==_ELLIPSE: edt=min(1.,edt+ds*dt) mang-=ms*dt if edt>=1.: malp=min(1.,malp+.012*dt) if dv: ea=max(0.,ea-dt/max(.001,fo*60.)) if ea<=0.: ast=_FLOAT; dv=0; ea=1.; edt=0.; mang=0.; malp=0.; ta=0. et=0.; lfp=0.; gt=-1.; pt=-1.; pids=[0,0,0,0]; segs=[]; las=[0.,0.,0.,0.]; cs=0 pid0=pid1=pid2=pid3=0 bg=_init_bg(W,H,s1,s2,s3); cd=_init_cd(W,H,s1,s2,s3,cw) prox=[] if ast==_FLOAT: for i in range(len(bg)): for j in range(i+1,len(bg)): dx=bg[i]['x']-bg[j]['x']; dy=bg[i]['y']-bg[j]['y'] dist=_m.sqrt(dx*dx+dy*dy) if dist<90: prox.append((dist,i,j)) prox.sort(); prox=prox[:int(root.par.Maxlinks.val)] # Line alpha for Level TOP (no Python alpha math in render) line_alpha=1. if ast==_LINES else (1.-lfp if ast==_LFADE else 0.) # Update line-label Text TOPs for i in range(4): did=pids[i] if i0.1: for idx in range(80): s=root.op(f'marq_geo/chr_sop_{idx:02d}') if s: s.par.fontsizex=mcsz root.store('prev_mcsz',mcsz) # Per-character marquee placement along ellipse rx_m=W*0.47-34; ry_m=H*0.43-24 for qi in range(4): ang=mang+qi*_m.pi/2 did_q=pids[qi] if qi