1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447(** Mask gradient utilities for creating gradient masks.
Provides utilities for mask-t-from-*, mask-t-to-*, mask-r-from-*,
mask-r-to-*, mask-b-from-*, mask-b-to-*, mask-l-from-*, mask-l-to-*,
mask-x-from-*, mask-x-to-*, mask-y-from-*, mask-y-to-*, mask-linear-from-*,
mask-linear-to-*, mask-radial-*, mask-radial-from-*, mask-radial-to-*,
mask-conic-from-*, mask-conic-to-*. *)moduleCss=Cascade.CssmoduleHandler=structopenStyleletpp_int=Pp.intletpp_float=Pp.floatopenCsstypedirection=|Top|Right|Bottom|Left|X|Y|Linear|Radial|Conictypeposition_end=From|Totypevalue=|Spacingoffloat(* calc(var(--spacing) * N) *)|Percentoffloat(* N% *)|Arbitraryofstring(* [value] *)typeradial_shape=Circle|Ellipsetyperadial_size=|Closest_corner|Closest_side|Farthest_corner|Farthest_side|Arbitrary_sizeofstringtyperadial_at_position=|Keywordofstring(* keywords like "bottom", "top left" *)|Bracketofstring(* arbitrary values like "25%" - stored without brackets *)typemask_angle=|Intofint(* mask-linear-45 → calc(1deg * 45); mask-linear-1 → 1deg *)|Arbofstring(* mask-linear-[3rad] → 171.887deg, read and converted at style time *)|Arb_negofstring(* -mask-linear-[3rad] → calc(171.887deg * -1) *)(* The colour a stop names, as the class spelled it. *)typestop_color=|PaletteofColor.color*int(* red-500 *)|Keyword_colorofCss.color*string(* transparent, current, inherit *)|Bracket_colorofstring(* [#fff], [red], [color:var(--c)]: the bracket's inner text as written *)|Color_varofstring(* (color:--x): the name after the dashes *)typet=|Positionofdirection*position_end*value|Var_refofdirection*position_end*stringoption*string(* (--x) or (<hint>:--x), the position read from a custom property: the
hint as written and the name after the dashes *)|Stopofdirection*position_end*stop_color*Color.opacity_modifier|Linear_angleofmask_angle|Conic_angleofmask_angle|Radial(* just mask-radial with no position *)|Radial_atofradial_at_position(* mask-radial-at-* *)|Radial_shapeofradial_shape(* mask-circle, mask-ellipse *)|Radial_sizeofradial_size(* mask-radial-closest-corner etc. *)letname="mask_gradient"(* Tailwind emits the mask-gradient utilities after the backgrounds and before
the other masks, which come before fill/stroke and padding. *)letpriority_=21letdirection_short=function|Top->"t"|Right->"r"|Bottom->"b"|Left->"l"|X->"x"|Y->"y"|Linear->"linear"|Radial->"radial"|Conic->"conic"letposition_end_name=functionFrom->"from"|To->"to"(* When using Spacing values, we need to emit the theme declaration for
--spacing so that :root, :host { --spacing: .25rem } appears *)letspacing_theme_declvalue=matchvaluewith|Spacing_->letdecl=Var.setTheme.spacing_varTheme.spacing_basein[decl]|Percent_|Arbitrary_->[](* Common mask image using List of three var references *)letmask_image_list:background_image=List[background_image_var_none"tw-mask-linear";background_image_var_none"tw-mask-radial";background_image_var_none"tw-mask-conic";](* A mask direction owns five variables: the slot holding the gradient it
contributes to [mask-image], and the colour and position of each of that
gradient's two stops. The names are written out rather than assembled, so
every one of them is a value the compiler checks. *)typestop_vars={slot:Css.background_imageVar.channel;from_position:Css.length_percentageVar.channel;to_position:Css.length_percentageVar.channel;from_color:Css.colorVar.channel;to_color:Css.colorVar.channel;}letstop_vars~slot~from_position~to_position~from_color~to_color={slot=Var.channelBackground_imageslot;from_position=Var.channelLength_percentagefrom_position;to_position=Var.channelLength_percentageto_position;from_color=Var.channelColorfrom_color;to_color=Var.channelColorto_color;}lettop_vars=stop_vars~slot:"tw-mask-top"~from_position:"tw-mask-top-from-position"~to_position:"tw-mask-top-to-position"~from_color:"tw-mask-top-from-color"~to_color:"tw-mask-top-to-color"letright_vars=stop_vars~slot:"tw-mask-right"~from_position:"tw-mask-right-from-position"~to_position:"tw-mask-right-to-position"~from_color:"tw-mask-right-from-color"~to_color:"tw-mask-right-to-color"letbottom_vars=stop_vars~slot:"tw-mask-bottom"~from_position:"tw-mask-bottom-from-position"~to_position:"tw-mask-bottom-to-position"~from_color:"tw-mask-bottom-from-color"~to_color:"tw-mask-bottom-to-color"letleft_vars=stop_vars~slot:"tw-mask-left"~from_position:"tw-mask-left-from-position"~to_position:"tw-mask-left-to-position"~from_color:"tw-mask-left-from-color"~to_color:"tw-mask-left-to-color"letlinear_vars=stop_vars~slot:"tw-mask-linear"~from_position:"tw-mask-linear-from-position"~to_position:"tw-mask-linear-to-position"~from_color:"tw-mask-linear-from-color"~to_color:"tw-mask-linear-to-color"letradial_vars=stop_vars~slot:"tw-mask-radial"~from_position:"tw-mask-radial-from-position"~to_position:"tw-mask-radial-to-position"~from_color:"tw-mask-radial-from-color"~to_color:"tw-mask-radial-to-color"letconic_vars=stop_vars~slot:"tw-mask-conic"~from_position:"tw-mask-conic-from-position"~to_position:"tw-mask-conic-to-position"~from_color:"tw-mask-conic-from-color"~to_color:"tw-mask-conic-to-color"(* [mask-x] and [mask-y] write both edges of their axis, so they own no
variables themselves. *)letvars_for=function|Top->top_vars|Right->right_vars|Bottom->bottom_vars|Left->left_vars|Linear->linear_vars|Radial->radial_vars|Conic->conic_vars|(X|Y)asd->invalid_arg("mask-"^direction_shortd^" has no variables")(* Everything between a gradient's parentheses, so a stop utility can replace
one stop without restating the others. *)letlinear_stops_var=Var.channelGradient_stop"tw-mask-linear-stops"letconic_stops_var=Var.channelGradient_stop"tw-mask-conic-stops"letradial_stops_var=Var.channelGradient_stop"tw-mask-radial-stops"(* The radial gradient's own geometry, which its stops read alongside the two
colour stops every direction owns. *)letradial_shape_var=Var.channelRadial_shape"tw-mask-radial-shape"letradial_size_var=Var.channelRadial_size"tw-mask-radial-size"letradial_position_var=Var.channelPosition_value"tw-mask-radial-position"(* The angle [mask-linear-45] and [mask-conic-45] turn their gradient by. *)letlinear_position_var=Var.channelAngle"tw-mask-linear-position"letconic_position_var=Var.channelAngle"tw-mask-conic-position"(* The [@property] initial values. A utility reads variables it does not set
itself, so the initial is both what the browser starts from and what the
inline renderer puts in the reference's place. *)letwhite_gradient:background_image=Linear_gradient(Default_direction,[Color_percentage(hex"#fff",None,None);Color_percentage(hex"#fff",None,None);])letstart_position:length_percentage=Pct0.letend_position:length_percentage=Pct100.letstart_color:Css.color=NamedBlackletend_color:Css.color=Transparentletno_rotation:angle=Deg0.letdefault_shape:Css.radial_shape=Ellipseletdefault_size:Css.radial_size=Farthest_cornerletdefault_position:position_value=Center(* Reading a mask variable. The [@property] rule gives it an initial value, so
the reference needs no fallback of its own. *)letreadvinitial=snd(Var.bindingvinitial)(* Writing one. *)letsetvvalue=Var.setvvalue(* The two stops a mask gradient runs between. *)letstopsv:gradient_stoplist=[Color_percentage(Var(readv.from_colorstart_color),Some(Var(readv.from_positionstart_position)),None);Color_percentage(Var(readv.to_colorend_color),Some(Var(readv.to_positionend_position)),None);]letedge_of:direction->gradient_direction*stop_vars=function|Top->(To_top,top_vars)|Right->(To_right,right_vars)|Bottom->(To_bottom,bottom_vars)|Left->(To_left,left_vars)|(X|Y|Linear|Radial|Conic)asd->invalid_arg("mask-"^direction_shortd^" is not an edge")(* [--tw-mask-<edge>] runs its two stops towards that edge. *)letedge_gradientdir=letside,v=edge_ofdirinsetv.slot(Linear_gradient(side,stopsv))(* A directional utility layers the four edges into [--tw-mask-linear]. The
edges it did not touch still contribute their initial white gradient, which
masks nothing. *)letedge_composition=setlinear_vars.slot(List[Var(readleft_vars.slotwhite_gradient);Var(readright_vars.slotwhite_gradient);Var(readbottom_vars.slotwhite_gradient);Var(readtop_vars.slotwhite_gradient);])letlinear_stops:gradient_stop=List(Direction(Angle(Var(readlinear_position_varno_rotation)))::stopslinear_vars)letconic_stops:gradient_stop=List(Position(Conic_position(conic_gradient_config~angle:(Var(readconic_position_varno_rotation))()))::stopsconic_vars)letlinear_stops_decls=letdecl,stops_ref=Var.bindinglinear_stops_varlinear_stopsin[decl;setlinear_vars.slot(Linear_gradient_varstops_ref)]letconic_stops_decls=letdecl,stops_ref=Var.bindingconic_stops_varconic_stopsin[decl;setconic_vars.slot(Conic_gradient_varstops_ref)](* The radial gradient reads its shape, size and position out of variables, so
a shape or size utility can move one of them without restating the stops.
[radial_geometry] is that [<ending-shape> <size> at <position>] prelude. *)letradial_geometry:gradient_position=Radial_position(radial_gradient_config~shape:(Var(readradial_shape_vardefault_shape))~size:(Var(readradial_size_vardefault_size))~position:(Var(readradial_position_vardefault_position))())letradial_stops:gradient_stop=List(Positionradial_geometry::stopsradial_vars)letradial_stops_decls=letdecl,stops_ref=Var.bindingradial_stops_varradial_stopsin[decl;setradial_vars.slot(Radial_gradient_varstops_ref)](* Common composite declarations *)letcomposite_decls=[webkit_mask_compositeSource_in;webkit_mask_compositeSource_in;mask_compositeIntersect;](* Helper to create mask-image declarations using typed functions *)letmask_image_decls=[webkit_mask_imagemask_image_list;webkit_mask_imagemask_image_list;mask_imagemask_image_list;](* @property rule helpers. *)letprop?(order=100)nameinitial=let_=orderinproperty~name:("--tw-mask-"^name)Universal~initial_value:initial~inherits:false()(* Common @property rules for mask-image vars *)letcommon_property_rules=concat[prop~order:55"linear""linear-gradient(#fff, #fff)";prop~order:56"radial""linear-gradient(#fff, #fff)";prop~order:57"conic""linear-gradient(#fff, #fff)";](* @property rules for directional gradient vars (left, right, bottom, top) *)letdirectional_gradient_property_rules=concat[prop~order:58"left""linear-gradient(#fff, #fff)";prop~order:59"right""linear-gradient(#fff, #fff)";prop~order:60"bottom""linear-gradient(#fff, #fff)";prop~order:61"top""linear-gradient(#fff, #fff)";](* @property rules for a specific direction's from/to vars. The order offset
varies by axis so that e.g. right endpoints sort before left endpoints in
the properties layer. A direction keeps one slot whichever utility emits
it: [mask-l-*] and the left half of [mask-x-*] name the same four custom
properties, so both ask for 66. *)letdirection_endpoint_rules?(order_base=62)dir_name=concat[prop~order:order_base(dir_name^"-from-position")"0%";prop~order:(order_base+1)(dir_name^"-to-position")"100%";prop~order:(order_base+2)(dir_name^"-from-color")"black";prop~order:(order_base+3)(dir_name^"-to-color")"transparent";](* Pre-compute property rules for all directions at module load time to avoid
re-registering property orders dynamically during tests. X/Y use different
order_bases so their sub-directions sort correctly in properties layer. *)lettop_property_rules=concat[common_property_rules;directional_gradient_property_rules;direction_endpoint_rules"top";]letright_property_rules=concat[common_property_rules;directional_gradient_property_rules;direction_endpoint_rules"right";]letbottom_property_rules=concat[common_property_rules;directional_gradient_property_rules;direction_endpoint_rules~order_base:66"bottom";]letleft_property_rules=concat[common_property_rules;directional_gradient_property_rules;direction_endpoint_rules~order_base:66"left";]letx_property_rules=concat[common_property_rules;directional_gradient_property_rules;direction_endpoint_rules~order_base:62"right";direction_endpoint_rules~order_base:66"left";]lety_property_rules=concat[common_property_rules;directional_gradient_property_rules;direction_endpoint_rules~order_base:62"top";direction_endpoint_rules~order_base:66"bottom";](* Linear/radial/conic property rules always include the position @property
(used by both angle and from/to modes) *)letlinear_property_rules=concat[common_property_rules;concat[prop~order:61"linear-position""0deg"];direction_endpoint_rules"linear";]letradial_property_rules=concat[common_property_rules;direction_endpoint_rules"radial";concat[prop~order:70"radial-shape""ellipse";prop~order:71"radial-size""farthest-corner";prop~order:72"radial-position""center";];]letconic_property_rules=concat[common_property_rules;concat[prop~order:61"conic-position""0deg"];direction_endpoint_rules"conic";]letproperty_rules_for_direction=function|Top->top_property_rules|Right->right_property_rules|Bottom->bottom_property_rules|Left->left_property_rules|X->x_property_rules|Y->y_property_rules|Linear->linear_property_rules|Radial->radial_property_rules|Conic->conic_property_rules(* A stop is a <length-percentage>. A bracket value cascade cannot read goes
into the sheet as the author wrote it. *)letposition_varv=functionFrom->v.from_position|To->v.to_positionletcolor_varv=functionFrom->v.from_color|To->v.to_colorletarbitrary_length_percentages:length_percentageoption=matchCascade.Cursor.try_parse_full_errCss.Values.read_length_percentage(Cascade.Cursor.of_strings)with|Oklp->Somelp|Error_->None(* The value a bracket carries: the text after any data-type hint, where [_]
is a space and a math function takes the spaces around its operators. The
hint chose the longhand and says nothing about the value. *)letbracket_valueinner=Parse.decode_arbitrary_value(Option.value(Parse.value_after_hintinner)~default:inner)letposition_decl?themevpos_endvalue=letvar=position_varvpos_endinmatchvaluewith|Spacing0.->(* The zero spacing step keeps its unit ([0px]) here, where a bare [0]
would be the folded length. *)setvar(Length(Px0.))|Spacingn->let_,len=Theme.spacing_calc_float?themeninsetvar(Lengthlen)|Percentp->setvar(Pctp)|Arbitraryraw->((* Whether or not what comes out is a length-percentage, it reaches the
sheet: the position is a token stream. *)letvalue=bracket_valuerawinmatcharbitrary_length_percentagevaluewith|Somelp->setvarlp|None->custom_property~layer:"utilities"(Var.css_namevar)value)(* Build the style for a directional mask position *)letbuild_directional_style?themedirpos_endvalue=letproperty_rules=property_rules_for_directiondirin(* Common declarations for all directional masks *)letcommon_decls=spacing_theme_declvalue@mask_image_decls@[edge_composition;edge_gradientdir;position_decl?theme(vars_fordir)pos_endvalue;]instyle~property_rules(common_decls@composite_decls)(* Build the style for mask-x (both left and right) *)letbuild_x_style?themepos_endvalue=letcommon_decls=spacing_theme_declvalue@mask_image_decls@[edge_composition;(* Right group first, then left group — interleaved order *)edge_gradientRight;position_decl?themeright_varspos_endvalue;edge_gradientLeft;position_decl?themeleft_varspos_endvalue;]instyle~property_rules:x_property_rules(common_decls@composite_decls)(* Build the style for mask-y (both top and bottom) *)letbuild_y_style?themepos_endvalue=letcommon_decls=spacing_theme_declvalue@mask_image_decls@[edge_composition;(* Top group first, then bottom group — interleaved order *)edge_gradientTop;position_decl?themetop_varspos_endvalue;edge_gradientBottom;position_decl?themebottom_varspos_endvalue;]instyle~property_rules:y_property_rules(common_decls@composite_decls)(* Build the style for mask-linear (generic linear gradient) *)letbuild_linear_style?themepos_endvalue=letcommon_decls=spacing_theme_declvalue@mask_image_decls@linear_stops_decls@[position_decl?themelinear_varspos_endvalue]instyle~property_rules:linear_property_rules(common_decls@composite_decls)(* Build the style for mask-radial position *)letbuild_radial_style?themepos_endvalue=letcommon_decls=spacing_theme_declvalue@mask_image_decls@radial_stops_decls@[position_decl?themeradial_varspos_endvalue]instyle~property_rules:radial_property_rules(common_decls@composite_decls)(* Build the style for mask-radial (no position) - produces no output in
Tailwind *)letbuild_radial_base_style=style[](* An arbitrary [mask-radial-at-[30%_30%]] is a position value, so it goes
through the value printer rather than reaching the sheet as written. *)letradial_at_positionraw:Css.position_valueoption=matchString.split_on_char' '(Parse.decode_arbitrary_valueraw)|>List.filter(funs->s<>"")with|[x;y]->(match(Css.parse_lengthx,Css.parse_lengthy)with|Somexv,Someyv->Some(XY(xv,yv))|_->None)|[v]->Option.map(fun(l:Css.length):Css.position_value->Singlel)(Css.parse_lengthv)|_->None(* A bracket [mask-radial-at-*] is a position value, and of_class has already
read it as one. The keyword form is the class's own words: it admits runs
no position value carries ([center center], [top top]), so it goes into the
sheet as the author spelled it. *)letbuild_radial_at_stylepos=matchposwith|Brackets->(matchradial_at_positionswith|Somep->style[setradial_position_varp]|None->style[custom_property~layer:"utilities"(Var.css_nameradial_position_var)s;])|Keywords->style[custom_property~layer:"utilities"(Var.css_nameradial_position_var)s;](* Build the style for mask-circle/mask-ellipse *)letbuild_radial_shape_style(shape:radial_shape)=letshape:Css.radial_shape=matchshapewithCircle->Circle|Ellipse->Ellipseinstyle[setradial_shape_varshape](* [mask-radial-[...]] takes whatever the author wrote, so a size cascade can
read becomes a typed radius and anything else goes into the sheet as
written. *)letradial_size_decls=lettyped:Css.radial_sizeoption=matchString.split_on_char' 'swith|[one]->Option.map(funl:Css.radial_size->Circle_radiusl)(Css.parse_lengthone)|[x;y]->(match(arbitrary_length_percentagex,arbitrary_length_percentagey)with|Somex,Somey->Some(Ellipse_radii(x,y))|_->None)|_->Noneinmatchtypedwith|Somesize->setradial_size_varsize|None->custom_property~layer:"utilities"(Var.css_nameradial_size_var)s(* Build the style for mask-radial size keywords and arbitrary sizes *)letbuild_radial_size_style(size:radial_size)=letsize_decl=matchsizewith|Closest_corner->setradial_size_varClosest_corner|Closest_side->setradial_size_varClosest_side|Farthest_corner->setradial_size_varFarthest_corner|Farthest_side->setradial_size_varFarthest_side|Arbitrary_sizes->radial_size_declsin(* Arbitrary sizes get full mask setup with property_rules, keywords just
set the variable without property rules *)matchsizewith|Arbitrary_size_->(* With no stops of its own the gradient falls back to the bare size,
which is the whole argument list a size utility leaves. *)letstops_ref=Var.reference_with_fallbackradial_stops_var(Position(Radial_position(radial_gradient_config~size:(Var(readradial_size_vardefault_size))())))inletcommon_decls=mask_image_decls@[setradial_vars.slot(Radial_gradient_varstops_ref);size_decl]instyle~property_rules:radial_property_rules(common_decls@composite_decls)|_->style[size_decl](* Build the style for mask-conic position *)letbuild_conic_style?themepos_endvalue=letcommon_decls=spacing_theme_declvalue@mask_image_decls@conic_stops_decls@[position_decl?themeconic_varspos_endvalue]instyle~property_rules:conic_property_rules(common_decls@composite_decls)(* Tailwind writes an arbitrary radian or turn angle into the position
variable as degrees; every other unit goes in as the author spelled it. *)letto_degrees:angle->angle=letdegn:angle=Deg(Float.round(n*.1000.0)/.1000.0)infunction|Radn->deg(n*.180.0/.Float.pi)|Turnn->deg(n*.360.0)|a->a(* A bracket angle cascade cannot read is kept as the tokens the author wrote,
so it still reaches the sheet through a typed angle. *)letinvalid_angles:angle=Invalid(Css.Values.read_invalid_value(Cascade.Cursor.of_strings))letarbitrary_angles:angleoption=matchCascade.Cursor.try_parse_full_errCss.Values.read_angle(Cascade.Cursor.of_strings)with|Oka->Some(to_degreesa)|Error_->None(* [--tw-mask-*-position] is an opaque [syntax: "*"] custom property, so the
value reproduces Tailwind's own output: a bare degree for the trivial
multipliers -1/0/1, and [calc(1deg * n)] otherwise. *)letangle_position_declvarangle=letnegateda:angle=Calc(Expr(Vala,Mul,Num(-1.0)))inmatchanglewith|Intnwhenn>=-1&&n<=1->setvar(Deg(float_of_intn))|Intn->setvar(Calc(Expr(Val(Deg1.0),Mul,Num(float_of_intn))))(* A bracket value that is not an angle still reaches the sheet, negation
and all, because the variable takes any token stream and Tailwind writes
the text through. On its own it is carried as the spec-invalid angle it
is; negated it is spelled out, because a typed [calc] over an invalid
operand loses the spaces around the operator that both tools write. *)|Arbs->setvar(Option.value(arbitrary_angles)~default:(invalid_angles))|Arb_negs->(matcharbitrary_angleswith|Somea->setvar(negateda)|None->custom_property~layer:"utilities"(Var.css_namevar)("calc("^s^" * -1)"))(* The angle shorthands take the whole gradient from the position variable
unless a stop utility has filled the stops variable in. *)letbuild_linear_angle_styleangle=letstops_ref=Var.reference_with_fallbacklinear_stops_var(Direction(Angle(Var(readlinear_position_varno_rotation))))inletdecls=mask_image_decls@[setlinear_vars.slot(Linear_gradient_varstops_ref);angle_position_decllinear_position_varangle;]instyle~property_rules:linear_property_rules(decls@composite_decls)letbuild_conic_angle_styleangle=letstops_ref=Var.reference_with_fallbackconic_stops_var(Direction(Angle(Var(readconic_position_varno_rotation))))inletdecls=mask_image_decls@[setconic_vars.slot(Conic_gradient_varstops_ref);angle_position_declconic_position_varangle;]instyle~property_rules:conic_property_rules(decls@composite_decls)(* A stop utility restates the gradient it belongs to, then writes its own end
of it. *)letstop_declsdirstop=letdir_decls=matchdirwith|X->[edge_gradientRight;stopright_vars]@[edge_gradientLeft;stopleft_vars]|Y->[edge_gradientTop;stoptop_vars]@[edge_gradientBottom;stopbottom_vars]|Linear->linear_stops_decls@[stoplinear_vars]|Radial->radial_stops_decls@[stopradial_vars]|Conic->conic_stops_decls@[stopconic_vars]|Top|Right|Bottom|Left->[edge_gradientdir;stop(vars_fordir)]in(* Linear, radial and conic own the whole mask-image slot, so only the four
edges have to layer themselves into [--tw-mask-linear]. *)letlinear_decl=matchdirwith|Linear|Radial|Conic->[]|Top|Right|Bottom|Left|X|Y->[edge_composition]inmask_image_decls@linear_decl@dir_decls(* Build the style for parenthesized var reference setting position *)letbuild_var_ref_styledirpos_endname=letproperty_rules=property_rules_for_directiondirinletmerge_key="mask-"^direction_shortdir^"-"^position_end_namepos_end^"-var-position"inletposition:length_percentage=Var(Var.bracketname)inletstopv=set(position_varvpos_end)positioninstyle~merge_key~property_rules(stop_declsdirstop@composite_decls)(* The colour a bracket stop names, read the way every colour family reads a
bracket: a var() is the reference it makes, the rest goes through the
colour reader. [None] is text the reader has no colour for. *)letbracket_colourinner:Css.coloroption=letvalue=Option.value(Parse.value_after_hintinner)~default:innerinifParse.is_varvaluethenOption.some(Color.bracket_var_color(Parse.decode_arbitrary_valuevalue))elseColor.parse_bracket_colorvalue(* The [current] keyword goes in as text because this custom property is a
token stream: a typed [Current] folds through it to a hex, where Tailwind
writes [currentcolor] for a gradient stop to resolve against the element.
Not a workaround for cascade's printer, which spells the keyword correctly
and positionally - [currentColor] bare, [currentcolor] inside a
function. *)letcolour_decl_of(c:Css.color)var=matchcwith|Current->custom_property~layer:"utilities"(Var.css_namevar)"currentcolor"|c->setvarc(* What a stop colour writes: the theme token a palette entry brings along, so
the sheet defines what it references, the stop every browser reads in the
open, and, when the modifier's mix needs a guard, the mix that goes behind
it. A bracket the colour reader has no colour for still reaches the sheet
as written, since the custom property takes any token stream. *)typestop_colour={tokens:Css.declarationlist;plain:Css.colorVar.channel->Css.declaration;mixed:(Css.colorVar.channel->Css.declaration)option;}letplain_colourc={tokens=[];plain=colour_decl_ofc;mixed=None}(* Tailwind writes the colour itself at 100%, and a mix for anything else. *)letopaque(opacity:Color.opacity_modifier)=matchopacitywith|No_opacity->true|Opacity_named_|Opacity_var_->false|Opacity_percent_|Opacity_arbitrary_|Opacity_bracket_percent_->Color.opacity_to_percentopacity>=100.(* A colour the modifier mixes, in the shape Tailwind writes it: the mix in
the open when every browser can read it, else the colour in the open and
the mix behind the guard. *)letmixed_colour~theme(c:Css.color)opacity=matchColor.bracket_color_opacity~themecopacitywith|Foldedvalue->plain_colourvalue|Guarded{fallback;mixed}->{tokens=[];plain=colour_decl_offallback;mixed=Option.some(funvar->setvarmixed);}letcolour_stop~themec(opacity:Color.opacity_modifier)=matchopacitywith|No_opacity->plain_colourc|_->mixed_colour~themecopacity(* A palette entry at an opacity: the hex carrying the alpha in the open, the
mix of the token behind the guard. An alpha read from a custom property has
no percentage to fold, so the open value is the colour itself. *)letpalette_stop~themecshade(opacity:Color.opacity_modifier)=letdecl,token=Color.Handler.color_binding~themecshadeinletreference:Css.color=Vartokeninifopaqueopacitythen{tokens=[decl];plain=(funvar->setvarreference);mixed=None;}elseletvalue=Color.to_css~themecshadeinletfallback=matchopacitywith|Opacity_named_|Opacity_var_->value|_->letpercent=Color.opacity_to_percentopacityinColor.opacity_fallback~theme~percentcshadevaluein{tokens=[decl];plain=(funvar->setvarfallback);mixed=Option.some(funvar->setvar(Color.mix_alphaopacityreference));}letstop_colour~themecolouropacity=matchcolourwith|Palette(c,shade)->palette_stop~themecshadeopacity|Keyword_color(c,_)->colour_stop~themecopacity|Color_varname->colour_stop~theme(Var(Var.bracketname))opacity|Bracket_colorinner->(matchbracket_colourinnerwith|Somec->colour_stop~themecopacity|None->lettext=bracket_valueinnerin{tokens=[];plain=(funvar->custom_property~layer:"utilities"(Var.css_namevar)text);mixed=None;})(* The sides a stop utility writes: both of an axis, else the one. *)letsides=function|X->[right_vars;left_vars]|Y->[top_vars;bottom_vars]|(Top|Right|Bottom|Left|Linear|Radial|Conic)asdir->[vars_fordir]letbuild_stop_style~themedirpos_endcolouropacity=letproperty_rules=property_rules_for_directiondirinlet{tokens;plain;mixed}=stop_colour~themecolouropacityinletstopv=plain(color_varvpos_end)inletprops=tokens@stop_declsdirstop@composite_declsinmatchmixedwith|None->style~property_rulesprops|Somemixed->letguarded=List.map(funv->mixed(color_varvpos_end))(sidesdir)instyle~property_rules~rules:(Some[Color.color_mix_supportsguarded])propsletto_styletheme=letbuild_directional_styledirpos_endvalue=build_directional_style~themedirpos_endvalueinletbuild_x_stylepos_endvalue=build_x_style~themepos_endvalueinletbuild_y_stylepos_endvalue=build_y_style~themepos_endvalueinletbuild_linear_stylepos_endvalue=build_linear_style~themepos_endvalueinletbuild_radial_stylepos_endvalue=build_radial_style~themepos_endvalueinletbuild_conic_stylepos_endvalue=build_conic_style~themepos_endvalueinfunction|Position(Top,pos_end,value)->build_directional_styleToppos_endvalue|Position(Right,pos_end,value)->build_directional_styleRightpos_endvalue|Position(Bottom,pos_end,value)->build_directional_styleBottompos_endvalue|Position(Left,pos_end,value)->build_directional_styleLeftpos_endvalue|Position(X,pos_end,value)->build_x_stylepos_endvalue|Position(Y,pos_end,value)->build_y_stylepos_endvalue|Position(Linear,pos_end,value)->build_linear_stylepos_endvalue|Position(Radial,pos_end,value)->build_radial_stylepos_endvalue|Position(Conic,pos_end,value)->build_conic_stylepos_endvalue|Linear_angleangle->build_linear_angle_styleangle|Conic_angleangle->build_conic_angle_styleangle|Radial->build_radial_base_style|Radial_atpos->build_radial_at_stylepos|Radial_shapeshape->build_radial_shape_styleshape|Radial_sizesize->build_radial_size_stylesize|Var_ref(dir,pos_end,_,name)->build_var_ref_styledirpos_endname|Stop(dir,pos_end,colour,opacity)->build_stop_style~themedirpos_endcolouropacity(* Tailwind sorts a layer's rules by the properties they set, walking the
fixed table its property-order list gives: mask-image, then a block per
side (--tw-mask-top, then its from-color, from-position, to-color and
to-position stops, then the same for right, bottom and left), then the
--tw-mask-linear, --tw-mask-radial and --tw-mask-conic blocks, then
mask-composite. Two rules sort on the first table index that separates
them, and one that runs out of properties sorts after one that keeps going.
Every utility here writes mask-image and carries on, so they share its
slot; inside it they sort by the index that separates them, spread four
apart to leave room for the two tie-breaks below. *)letmask_image_rank=209letfirst_stop_rank=210letproperty_suborderindex=Utility.Property_order.slotmask_image_rank+(4*(index-first_stop_rank))(* mask-x-* and mask-y-* write the opposite side too, so they lead the
mask-r-* and mask-t-* they share a first property with. Tailwind breaks the
remaining ties on the class name, where the (--var) spellings lead the
lengths and percentages. *)letstop_suborderdirpos_end~color~var=letblock=matchdirwith|Top|Y->210|Right|X->215|Bottom->220|Left->225|Linear->231|Radial->239|Conic->245inletstop=match(pos_end,color)with|From,true->1|From,false->2|To,true->3|To,false->4inletsides=matchdirwithX|Y->0|_->2inproperty_suborder(block+stop)+sides+ifvarthen0else1letsuborder=function|Stop(dir,pos_end,Color_var_,_)->stop_suborderdirpos_end~color:true~var:true|Stop(dir,pos_end,(Palette_|Keyword_color_|Bracket_color_),_)->stop_suborderdirpos_end~color:true~var:false|Var_ref(dir,pos_end,_,_)->stop_suborderdirpos_end~color:false~var:true|Position(dir,pos_end,_)->stop_suborderdirpos_end~color:false~var:false|Linear_angle_->property_suborder231|Radial->property_suborder236|Radial_size(Arbitrary_size_)->property_suborder238|Conic_angle_->property_suborder245(* mask-circle, the radial size keywords and mask-radial-at-* write a
--tw-mask-radial-* variable and no mask-image, so each closes the slot of
the variable it names: --tw-mask-radial-shape (237),
--tw-mask-radial-size (238) and --tw-mask-radial-position (239). The
class name orders the ones sharing a slot. *)|Radial_shape_->Utility.Property_order.last237|Radial_size_->Utility.Property_order.last238|Radial_at_->Utility.Property_order.last239(* Tailwind's <number>: an optional sign, digits with at most one point and a
digit after it, and an optional exponent. It is what a percentage token
opens with, whether or not the token then reads as a stop. *)letis_css_numbers=letn=String.lengthsinletdigiti=i<n&&s.[i]>='0'&&s.[i]<='9'inletrecdigitsi=ifdigitithendigits(i+1)elseiinletsignedi=ifi<n&&(s.[i]='+'||s.[i]='-')theni+1elseiinleti=signed0inletj=digitsiinletj=ifj<n&&s.[j]='.'thendigits(j+1)elsejinifnot(j>i&&digit(j-1))thenfalseelseifj=nthentrueelseifs.[j]='e'||s.[j]='E'thenletk=signed(j+1)inletl=digitskinl>k&&l=nelsefalseletpercentage_numbers=ifString.ends_with~suffix:"%"sthenOption.some(String.subs0(String.lengths-1))elseOption.none(* A percentage stop is a whole number with no sign: 12.5%, 05% and -25% are
percentages Tailwind refuses rather than positions it writes through. *)letwhole_percentagenumber=matchParse.decimal_intnumberwith|Somen->n>=0&&number.[0]<>'-'|None->false(* A bare stop is a spacing step, read in quarters, or a whole percentage,
neither of which takes a sign. *)letbare_positionsuffix=ifsuffix=""||suffix.[0]='-'thenOption.noneelsematchpercentage_numbersuffixwith|Somenumber->ifwhole_percentagenumberthenOption.some(Percent(Float.of_int(int_of_stringnumber)))elseOption.none|None->(matchParse.spacing_value~name:"mask stop"suffixwith|Okn->Option.some(Spacingn)|Error_->Option.none)(* Tailwind's own test for a math function: one of its names followed by a
paren anywhere in the value. *)letmath_functions=["calc";"min";"max";"clamp";"mod";"rem";"sin";"cos";"tan";"asin";"acos";"atan";"atan2";"pow";"sqrt";"hypot";"log";"exp";"round";]letcontains~subs=letn=String.lengthsandm=String.lengthsubinletrecfromi=i+m<=n&&(String.subsim=sub||from(i+1))infrom0lethas_math_callvalue=String.containsvalue'('&&List.exists(funf->contains~sub:(f^"(")value)math_functions(* The colour keywords Tailwind knows: the named colours, [transparent],
[currentcolor] and the system colours, in any case. [inherit] is not
one. *)letis_colour_keywordvalue=matchCss.Values.read_color_keyword_of_string(String.lowercase_asciivalue)with|Some(Named_|Transparent|Current|System_)->true|Some_|None->falseletis_colour_tokenvalue=(value<>""&&value.[0]='#')||Parse.is_css_color_fnvalue||String.starts_with~prefix:"--alpha("value||is_colour_keywordvalue(* Which longhand a bracket stop lands in. The hint decides when the author
wrote one; otherwise Tailwind infers: a var() or a math function it does
not read, a percentage is one, a hex, a colour function or a colour keyword
is the colour, and anything else is the position. *)typebracket_kind=Colour_stop|Percentage_stop|Position_stopletinfer_kindvalue=ifParse.is_varvalue||has_math_callvaluethenPosition_stopelsematchpercentage_numbervaluewith|Somenumberwhenis_css_numbernumber->Percentage_stop|Some_|None->ifis_colour_tokenvaluethenColour_stopelsePosition_stopletbracket_kindhintvalue=matchhintwith|Some"color"->Colour_stop|Some"percentage"->Percentage_stop|Some_->Position_stop|None->infer_kindvalue(* A bracket stop. An empty hint names no utility, and so does a value that is
blank or would not stay inside its declaration. A colour the reader cannot
read is written through, unless it holds a call Tailwind would have
resolved and refused, or a modifier that has nothing to mix. A position
takes no modifier. *)letbracket_stopdirpos_endopacityinner=letinvalid=Error(`Msg("Invalid mask stop: ["^inner^"]"))inletmodified=opacity<>Color.No_opacityinmatchParse.value_after_hintinnerwith|None->invalid|Somevalue->(lethint=Option.mapfst(Parse.data_type_hintinner)inletvalue=Parse.decode_arbitrary_valuevalueinifString.trimvalue=""||not(Parse.is_declaration_valuevalue)theninvalidelsematchbracket_kindhintvaluewith|Colour_stop->ifbracket_colourinner=None&&(modified||Parse.holds_unresolved_callvalue)theninvalidelseOk(Stop(dir,pos_end,Bracket_colorinner,opacity))|Percentage_stop->(matchpercentage_numbervaluewith|Somenumberwhenwhole_percentagenumber&¬modified->Ok(Position(dir,pos_end,Arbitraryinner))|Some_|None->invalid)|Position_stop->ifmodifiedtheninvalidelseOk(Position(dir,pos_end,Arbitraryinner)))(* The (--x) shorthand. Tailwind splits a hint off at the first colon and
takes what follows only when it names a custom property: the hint as
written, and the name after the dashes, which the degenerate (--) leaves
empty. *)letparen_varsuffix=letn=String.lengthsuffixinifn<2||suffix.[0]<>'('||suffix.[n-1]<>')'thenOption.noneelseletinner=String.subsuffix1(n-2)inlethint,name=matchParse.data_type_hintinnerwith|Some(hint,name)->(Option.somehint,name)|None->(Option.none,inner)inifhint=Some""||(not(String.starts_with~prefix:"--"name))||not(Parse.is_declaration_valuename)thenOption.noneelseOption.some(hint,String.subname2(String.lengthname-2))(* A bare stop: Tailwind reads a colour first, [inherit], [transparent],
[current] or a palette entry, and a step or a percentage after, which take
no modifier. *)letbare_stop~themedirpos_endopacitybase=letkeywordc=Option.some(Stop(dir,pos_end,Keyword_color(c,base),opacity))inmatchbasewith|"transparent"->keywordCss.Transparent|"current"->keywordCss.Current|"inherit"->keywordCss.Inherit|_->(matchColor.shade_of_strings~theme(String.split_on_char'-'base)with|Ok(c,shade)->Option.some(Stop(dir,pos_end,Palette(c,shade),opacity))|Error_whenopacity<>Color.No_opacity->Option.none|Error_->Option.map(funv->Position(dir,pos_end,v))(bare_positionbase))(* Parse directional from/to with support for values and paren refs *)letparse_directional~themedirpos_endrest=letbase,opacity=Color.parse_opacity_modifier~theme(String.concat"-"rest)inletinvalid=Error(`Msg("Invalid mask-"^direction_shortdir^"-"^position_end_namepos_end^" value"))inmatchparen_varbasewith|Some(Some"color",name)->Ok(Stop(dir,pos_end,Color_varname,opacity))|Some(Some"percentage",_)->invalid|Some_whenopacity<>Color.No_opacity->invalid|Some(hint,name)->Ok(Var_ref(dir,pos_end,hint,name))|None->(ifParse.is_bracket_valuebasethenbracket_stopdirpos_endopacity(Parse.bracket_innerbase)elsematchbare_stop~themedirpos_endopacitybasewith|Somestop->Okstop|None->invalid)letof_classthemeclass_name=letparts=Parse.split_classclass_nameinmatchpartswith(* mask-t-from-*, mask-t-to-* *)|"mask"::"t"::"from"::restwhenrest<>[]->parse_directional~themeTopFromrest|"mask"::"t"::"to"::restwhenrest<>[]->parse_directional~themeTopTorest(* mask-r-from-*, mask-r-to-* *)|"mask"::"r"::"from"::restwhenrest<>[]->parse_directional~themeRightFromrest|"mask"::"r"::"to"::restwhenrest<>[]->parse_directional~themeRightTorest(* mask-b-from-*, mask-b-to-* *)|"mask"::"b"::"from"::restwhenrest<>[]->parse_directional~themeBottomFromrest|"mask"::"b"::"to"::restwhenrest<>[]->parse_directional~themeBottomTorest(* mask-l-from-*, mask-l-to-* *)|"mask"::"l"::"from"::restwhenrest<>[]->parse_directional~themeLeftFromrest|"mask"::"l"::"to"::restwhenrest<>[]->parse_directional~themeLeftTorest(* mask-x-from-*, mask-x-to-* *)|"mask"::"x"::"from"::restwhenrest<>[]->parse_directional~themeXFromrest|"mask"::"x"::"to"::restwhenrest<>[]->parse_directional~themeXTorest(* mask-y-from-*, mask-y-to-* *)|"mask"::"y"::"from"::restwhenrest<>[]->parse_directional~themeYFromrest|"mask"::"y"::"to"::restwhenrest<>[]->parse_directional~themeYTorest(* mask-linear-from-*, mask-linear-to-* *)|"mask"::"linear"::"from"::restwhenrest<>[]->parse_directional~themeLinearFromrest|"mask"::"linear"::"to"::restwhenrest<>[]->parse_directional~themeLinearTorest(* mask-linear-N (angle), mask-linear-[arb] *)|["mask";"linear";n]->(ifParse.is_bracket_valuenthenletinner=Parse.bracket_innerninOk(Linear_angle(Arbinner))elsematchParse.decimal_intnwith|Somei->Ok(Linear_angle(Inti))|None->Error(`Msg"Invalid mask-linear angle value"))(* -mask-linear-N (negative angle), -mask-linear-[arb] *)|["";"mask";"linear";n]->(ifParse.is_bracket_valuenthenletinner=Parse.bracket_innerninOk(Linear_angle(Arb_neginner))elsematchParse.decimal_intnwith|Somei->Ok(Linear_angle(Int(-i)))|None->Error(`Msg"Invalid negative mask-linear angle value"))(* mask-radial *)|["mask";"radial"]->OkRadial(* mask-radial-at-* *)|"mask"::"radial"::"at"::restwhenrest<>[]->letposition=String.concat" "restin(* Handle arbitrary values - strip brackets *)ifParse.is_bracket_valuepositionthenletinner=Parse.bracket_innerpositionin(* The channel is this family's longhand, so a bracket no position
reader took still reaches it; [build_radial_at_style] already
writes such a bracket verbatim. *)ifradial_at_positioninner=None&&Parse.arbitrary_declaration_valueinner=NonethenError(`Msg("Invalid mask-radial-at position: "^position))elseOk(Radial_at(Bracketinner))else(* Validate keyword positions: top, bottom, left, right, center and
combinations *)letis_valid_keyword=List.for_all(funw->List.memw["top";"bottom";"left";"right";"center"])restinifis_valid_keywordthenOk(Radial_at(Keywordposition))elseError(`Msg("Invalid mask-radial-at position: "^position))(* mask-radial-from-*, mask-radial-to-* *)|"mask"::"radial"::"from"::restwhenrest<>[]->parse_directional~themeRadialFromrest|"mask"::"radial"::"to"::restwhenrest<>[]->parse_directional~themeRadialTorest(* mask-conic-from-*, mask-conic-to-* *)|"mask"::"conic"::"from"::restwhenrest<>[]->parse_directional~themeConicFromrest|"mask"::"conic"::"to"::restwhenrest<>[]->parse_directional~themeConicTorest(* mask-conic-N (angle), mask-conic-[arb] *)|["mask";"conic";n]->(ifParse.is_bracket_valuenthenletinner=Parse.bracket_innerninOk(Conic_angle(Arbinner))elsematchParse.decimal_intnwith|Somei->Ok(Conic_angle(Inti))|None->Error(`Msg"Invalid mask-conic angle value"))(* -mask-conic-N (negative angle), -mask-conic-[arb] *)|["";"mask";"conic";n]->(ifParse.is_bracket_valuenthenletinner=Parse.bracket_innerninOk(Conic_angle(Arb_neginner))elsematchParse.decimal_intnwith|Somei->Ok(Conic_angle(Int(-i)))|None->Error(`Msg"Invalid negative mask-conic angle value"))(* mask-circle, mask-ellipse *)|["mask";"circle"]->Ok(Radial_shapeCircle)|["mask";"ellipse"]->Ok(Radial_shapeEllipse)(* mask-radial size keywords *)|["mask";"radial";"closest";"corner"]->Ok(Radial_sizeClosest_corner)|["mask";"radial";"closest";"side"]->Ok(Radial_sizeClosest_side)|["mask";"radial";"farthest";"corner"]->Ok(Radial_sizeFarthest_corner)|["mask";"radial";"farthest";"side"]->Ok(Radial_sizeFarthest_side)(* mask-radial-[size] - arbitrary size *)|["mask";"radial";arb]whenParse.is_bracket_valuearb->letsize_value=Parse.bracket_innerarbinletsize_value=Parse.decode_underscoressize_valueinifnot(Parse.is_declaration_valuesize_value)thenError(`Msg("Invalid mask-radial size: "^size_value))elseOk(Radial_size(Arbitrary_sizesize_value))|_->Error(`Msg"Not a mask gradient utility")letformat_value=function|Spacingn->ifFloat.is_integernthenstring_of_int(int_of_floatn)elsestring_of_floatn|Percentp->ifFloat.is_integerpthenpp_int(int_of_floatp)^"%"elsepp_floatp^"%"|Arbitraryv->"["^v^"]"letstop_color_class=function|Palette(c,shade)->Color.color_to_stringc^ifColor.is_shadelesscthen""else"-"^string_of_intshade|Keyword_color(_,name)->name|Bracket_colorinner->"["^inner^"]"|Color_varname->"(color:--"^name^")"letto_class=function|Position(dir,pos_end,value)->"mask-"^direction_shortdir^"-"^position_end_namepos_end^"-"^format_valuevalue|Var_ref(dir,pos_end,hint,name)->lethint=matchhintwithSomeh->h^":"|None->""in"mask-"^direction_shortdir^"-"^position_end_namepos_end^"-("^hint^"--"^name^")"|Stop(dir,pos_end,colour,opacity)->"mask-"^direction_shortdir^"-"^position_end_namepos_end^"-"^stop_color_classcolour^Color.opacity_suffixopacity|Linear_angle(Intn)->ifn<0then"-mask-linear-"^string_of_int(-n)else"mask-linear-"^string_of_intn|Linear_angle(Arbs)->"mask-linear-["^s^"]"|Linear_angle(Arb_negs)->"-mask-linear-["^s^"]"|Conic_angle(Intn)->ifn<0then"-mask-conic-"^string_of_int(-n)else"mask-conic-"^string_of_intn|Conic_angle(Arbs)->"mask-conic-["^s^"]"|Conic_angle(Arb_negs)->"-mask-conic-["^s^"]"|Radial->"mask-radial"|Radial_at(Keywordpos)->"mask-radial-at-"^String.concat"-"(String.split_on_char' 'pos)|Radial_at(Bracketpos)->"mask-radial-at-["^pos^"]"|Radial_shapeCircle->"mask-circle"|Radial_shapeEllipse->"mask-ellipse"|Radial_sizeClosest_corner->"mask-radial-closest-corner"|Radial_sizeClosest_side->"mask-radial-closest-side"|Radial_sizeFarthest_corner->"mask-radial-farthest-corner"|Radial_sizeFarthest_side->"mask-radial-farthest-side"|Radial_size(Arbitrary_sizes)->(* The size is held decoded, so the bracket it goes back into spells a
space [_] and an underscore [\_]. *)"mask-radial-["^Parse.encode_underscoress^"]"letexamples=[Linear_angle(Int0)]endopenHandlermoduleUtility_factory=Utility.Make(Handler)letutility=Utility_factory.v(* Convenience functions for creating mask gradient utilities *)letmask_t_fromvalue=utility(Position(Top,From,value))letmask_t_tovalue=utility(Position(Top,To,value))letmask_r_fromvalue=utility(Position(Right,From,value))letmask_r_tovalue=utility(Position(Right,To,value))letmask_b_fromvalue=utility(Position(Bottom,From,value))letmask_b_tovalue=utility(Position(Bottom,To,value))letmask_l_fromvalue=utility(Position(Left,From,value))letmask_l_tovalue=utility(Position(Left,To,value))letmask_x_fromvalue=utility(Position(X,From,value))letmask_x_tovalue=utility(Position(X,To,value))letmask_y_fromvalue=utility(Position(Y,From,value))letmask_y_tovalue=utility(Position(Y,To,value))letmask_linear_fromvalue=utility(Position(Linear,From,value))letmask_linear_tovalue=utility(Position(Linear,To,value))letmask_radial=utilityRadialletmask_radial_atpos=utility(Radial_atpos)letmask_radial_fromvalue=utility(Position(Radial,From,value))letmask_radial_tovalue=utility(Position(Radial,To,value))letmask_conic_fromvalue=utility(Position(Conic,From,value))letmask_conic_tovalue=utility(Position(Conic,To,value))