--[[ ==README== No GUI, pretty straightforward. For example, to make a line bend in an arc and transition from blue to red, do this: {\frz10\c&HFF0000&}This is a line of tex{\frz350\c&H0000FF&}t Run the automation and it'll add a tag before each character to make the rotation and color transition change smoothly across the line. Rotations are locked to less than 180 degree rotations. If you want a bend of more than 180, then split it up into multiple rotations of less than 180 each. This script is meant for convenience above all, so it runs with a single button press and no time-consuming options menu. ]]-- script_name = "Gradient by character" script_description = "Smoothly transforms tags across your line, by character." script_version = "1.3.1" script_author = "lyger" script_namespace = "lyger.GradientByChar" local DependencyControl = require("l0.DependencyControl") local rec = DependencyControl{ feed = "https://raw.githubusercontent.com/TypesettingTools/lyger-Aegisub-Scripts/master/DependencyControl.json", { {"lyger.LibLyger", version = "2.0.0", url = "http://github.com/TypesettingTools/lyger-Aegisub-Scripts"}, "aegisub.util", "aegisub.re" } } local LibLyger, util, re = rec:requireModules() local libLyger = LibLyger() function grad_char(sub,sel) libLyger:set_sub(sub, sel) for si,li in ipairs(sel) do --Read in the line this_line=libLyger.lines[li] --Make sure line starts with tags if this_line.text:find("^{")==nil then this_line.text="{}"..this_line.text end --Turn all \1c tags into \c tags, just for convenience this_line.text=this_line.text:gsub("\\1c","\\c") --Make line table this_table={} x=1 for thistag,thistext in this_line.text:gsub("}","}\t"):gmatch("({[^{}]*})([^{}]*)") do this_table[x]={tag=thistag,text=thistext:gsub("\t","")} x=x+1 end if #this_table<2 then aegisub.log("There must be more than one tag block in the line!") return end --Transform these tags transform_tags={ "c","2c","3c","4c", "alpha","1a","2a","3a", "fscx","fscy","fax","fay", "frx","fry","frz", "fs","fsp", "bord","shad", "xbord","ybord","xshad","yshad", "blur","be" } --Make state table this_state = LibLyger.make_state_table(this_table,transform_tags) --Style lookup this_style = libLyger:style_lookup(this_line) --Running record of the state of the line current_state={} --Outer control loop for i=2,#this_table,1 do --Update current state for ctag,cval in pairs(this_state[i-1]) do current_state[ctag]=cval end --Stores state of each character, to prevent redundant tags char_state=util.deep_copy(current_state) --Local function for interpolation local function handle_interpolation(factor,tag,sval,eval) local param_type, ivalue = libLyger.param_type, "" --Handle differently depending on the type of tag if param_type[tag]=="alpha" then ivalue=interpolate_alpha(factor,sval,eval) elseif param_type[tag]=="color" then ivalue=interpolate_color(factor,sval,eval) elseif param_type[tag]=="angle" then nstart=tonumber(sval) nend=tonumber(eval) --Use "Rotate in shortest direction" by default nstart=nstart%360 nend=nend%360 ndelta=nend-nstart if math.abs(ndelta)>180 then nstart=nstart+(ndelta*360)/math.abs(ndelta) end --Interpolate nvalue=interpolate(factor,nstart,nend) if nvalue<0 then nvalue=nvalue+360 end --Convert to string ivalue=libLyger.float2str(nvalue) elseif param_type[tag]=="number" then nstart=tonumber(sval) nend=tonumber(eval) --Interpolate and convert to string ivalue=libLyger.float2str(interpolate(factor,nstart,nend)) end return ivalue end local ttext=this_table[i-1].text if ttext:len()>0 then --Rebuilt text local rtext="" --Skip the first character local first=true --Starting values idx=1 matches=re.find(ttext,'\\\\[Nh]|\\X') total=#matches for _,match in ipairs(matches) do ch=match.str if not first then --Interpolation factor factor=idx/total idx=idx+1 --Do nothing if the character is a space if ch:find("%s")~=nil then rtext=rtext..ch else --The tags in and out of the time statement local non_time_tags="" --Go through all the state tags in this tag block for ttag,tparam in pairs(this_state[i]) do --Figure out the starting state of the param local sparam=current_state[ttag] if sparam==nil then sparam=this_style[ttag] end if type(sparam)~="number" then sparam=sparam:gsub("%)","") end--Just in case a \t tag snuck in --Prevent redundancy if sparam~=tparam then --The string version of the interpolated parameter local iparam=handle_interpolation(factor,ttag,sparam,tparam) if iparam~=tostring(char_state[ttag]) then non_time_tags=non_time_tags.."\\"..ttag..iparam char_state[ttag]=iparam end end end if non_time_tags:len() < 1 then --If no tags were added, do nothing rtext=rtext..ch else --The final tag, with a star to indicate it was added through interpolation rtext=rtext.."{*"..non_time_tags.."}"..ch end end else rtext=rtext..ch end first=false end this_table[i-1].text=rtext end end rebuilt_text="" for i,val in pairs(this_table) do rebuilt_text=rebuilt_text..val.tag..val.text end this_line.text=rebuilt_text sub[li]=this_line end aegisub.set_undo_point(script_name) end function remove_grad_char(sub,sel) for si,li in ipairs(sel) do this_line=sub[li] this_line.text=this_line.text:gsub("{%*[^{}]*}","") sub[li]=this_line end end -- Register the macro rec:registerMacros{ {"Apply Gradient", script_description, grad_char}, {"Remove Gradient", "Removes gradient generated by #{script_name}", remove_grad_char} }