@@ -1186,6 +1186,224 @@ exports.parsePosition = function parsePosition(val, validPositions = positions)
11861186 return null ;
11871187} ;
11881188
1189+ /**
1190+ * https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes
1191+ * https://drafts.csswg.org/cssom/#ref-for-value-def-shape
1192+ *
1193+ * Used for `clip-path`, `offset-path`, and `shape-outside`.
1194+ *
1195+ * <resource> | [<basic-shape> || <geometry-box>] | none
1196+ */
1197+ exports . parseBasicShape = function parseBasicShape ( val ) {
1198+ const variable = exports . parseCustomVariable ( val ) ;
1199+ if ( variable ) {
1200+ return variable ;
1201+ }
1202+
1203+ const resource = exports . parseResource ( val ) ;
1204+ if ( resource ) {
1205+ return resource ;
1206+ }
1207+
1208+ const shapeRegEx = new RegExp ( `^(circle|ellipse|inset|path|polygon)\\(${ ws } (.*)${ ws } \\)$` , 'i' ) ;
1209+ let res = shapeRegEx . exec ( val ) ;
1210+ if ( res ) {
1211+ const [ , fn , stringArgs ] = res ;
1212+ const parsedArgs = [ ] ;
1213+
1214+ /**
1215+ * circle(<shape-radius>? [at <position>]?)
1216+ *
1217+ * <shape-radius> should be positive <length-percentage>, closest-side, or
1218+ * farthest-side.
1219+ * <shape-radius> is in browsers resolved value only if user defined.
1220+ * <position> default to 50% 50% in Chrome.
1221+ * <position> default to center center in Firefox.
1222+ */
1223+ if ( fn === 'circle' ) {
1224+ const circleRegEx1 = new RegExp ( `^(.+)${ whitespace } +(at${ whitespace } +(.+))?$` , 'i' ) ;
1225+ const circleRegEx2 = new RegExp ( `^at${ whitespace } +(.+)$` , 'i' ) ;
1226+ let radius ;
1227+ let position ;
1228+
1229+ if ( ( res = circleRegEx1 . exec ( stringArgs ) ) ) {
1230+ [ , radius , , position = '' ] = res ;
1231+ } else if ( ( res = circleRegEx2 . exec ( stringArgs ) ) ) {
1232+ [ , position ] = res ;
1233+ } else {
1234+ radius = stringArgs ;
1235+ }
1236+ if ( radius ) {
1237+ radius =
1238+ exports . parseLengthOrPercentage ( radius , false , true ) ||
1239+ exports . parseKeyword ( radius , [ 'closest-side' , 'farthest-side' ] ) ;
1240+ if ( radius === null ) {
1241+ return null ;
1242+ }
1243+ parsedArgs . push ( radius ) ;
1244+ }
1245+ if ( position ) {
1246+ position = exports . parsePosition ( position ) ;
1247+ if ( position === null ) {
1248+ return null ;
1249+ }
1250+ parsedArgs . push ( 'at' , position ) ;
1251+ } else {
1252+ parsedArgs . push ( 'at center center' ) ;
1253+ }
1254+ return `circle(${ parsedArgs . join ( ' ' ) } )` ;
1255+ }
1256+
1257+ /**
1258+ * ellipse(<shape-radius>{2}? [at <position>]?)
1259+ *
1260+ * <shape-radius> should be positive <length-percentage>, closest-side, or
1261+ * farthest-side.
1262+ * <shape-radius> is in browsers resolved value only if user defined.
1263+ * <position> default to 50% 50% in Chrome.
1264+ * <position> default to center center in Firefox.
1265+ */
1266+ if ( fn === 'ellipse' ) {
1267+ const circleRegEx1 = new RegExp ( `^(.+)${ whitespace } +(at${ whitespace } +(.+))?$` , 'i' ) ;
1268+ const circleRegEx2 = new RegExp ( `^at${ whitespace } +(.+)$` , 'i' ) ;
1269+ let radii ;
1270+ let position ;
1271+
1272+ if ( ( res = circleRegEx1 . exec ( stringArgs ) ) ) {
1273+ [ , radii , , position = '' ] = res ;
1274+ } else if ( ( res = circleRegEx2 . exec ( stringArgs ) ) ) {
1275+ [ , position ] = res ;
1276+ } else {
1277+ radii = stringArgs ;
1278+ }
1279+ if ( radii ) {
1280+ [ radii ] = exports . splitTokens ( radii ) ;
1281+ if ( radii . length !== 2 ) {
1282+ return null ;
1283+ }
1284+ let [ rx , ry ] = radii ;
1285+ rx =
1286+ exports . parseLengthOrPercentage ( rx , false , true ) ||
1287+ exports . parseKeyword ( rx , [ 'closest-side' , 'farthest-side' ] ) ;
1288+ ry =
1289+ exports . parseLengthOrPercentage ( ry , false , true ) ||
1290+ exports . parseKeyword ( ry , [ 'closest-side' , 'farthest-side' ] ) ;
1291+ if ( ! ( rx && ry ) ) {
1292+ return null ;
1293+ }
1294+ parsedArgs . push ( rx , ry ) ;
1295+ }
1296+ if ( position ) {
1297+ position = exports . parsePosition ( position ) ;
1298+ if ( position === null ) {
1299+ return null ;
1300+ }
1301+ parsedArgs . push ( 'at' , exports . parsePosition ( position ) ) ;
1302+ } else {
1303+ parsedArgs . push ( 'at center center' ) ;
1304+ }
1305+ return `ellipse(${ parsedArgs . join ( ' ' ) } )` ;
1306+ }
1307+
1308+ /**
1309+ * inset(<length-percentage>{1,4} [round <border-radius>]?)
1310+ */
1311+ if ( fn === 'inset' ) {
1312+ const insetRegEx = new RegExp ( `^(.+)${ whitespace } +round${ whitespace } +(.+)$` , 'i' ) ;
1313+ let corners ;
1314+ let radii ;
1315+
1316+ if ( ( res = insetRegEx . exec ( stringArgs ) ) ) {
1317+ [ , corners , radii ] = res ;
1318+ } else {
1319+ corners = stringArgs ;
1320+ }
1321+
1322+ [ corners ] = exports . splitTokens ( corners ) ;
1323+ const { length : cornerLength } = corners ;
1324+
1325+ if (
1326+ cornerLength > 4 ||
1327+ ! corners . every ( ( corner , i ) => ( corners [ i ] = exports . parseLengthOrPercentage ( corner ) ) )
1328+ ) {
1329+ return null ;
1330+ }
1331+ corners = corners . filter ( ( corner , i ) => {
1332+ if ( i > 1 ) {
1333+ return corner !== corners [ i - 2 ] ;
1334+ }
1335+ if ( i === 1 ) {
1336+ return corner !== corners [ i - 1 ] ;
1337+ }
1338+ return true ;
1339+ } ) ;
1340+ parsedArgs . push ( corners . join ( ' ' ) ) ;
1341+ if ( radii ) {
1342+ radii = exports . parseBorderRadius ( radii ) ;
1343+ if ( radii === null ) {
1344+ return null ;
1345+ }
1346+ if ( radii !== '0%' && radii !== '0px' ) {
1347+ parsedArgs . push ( 'round' , radii ) ;
1348+ }
1349+ }
1350+ return `inset(${ parsedArgs . join ( ' ' ) } )` ;
1351+ }
1352+
1353+ /**
1354+ * path([<fill-rule>,]? <string>)
1355+ *
1356+ * TODO: validate <string> as a valid path definition.
1357+ */
1358+ if ( fn === 'path' ) {
1359+ const [ args ] = exports . splitTokens ( stringArgs , / , / ) ;
1360+ if ( args . length === 2 ) {
1361+ const fill = exports . parseKeyword ( args [ 0 ] , [ 'evenodd' , 'nonzero' ] ) ;
1362+ const string = exports . parseString ( args [ 1 ] ) ;
1363+ if ( ! ( fill && string ) ) {
1364+ return null ;
1365+ }
1366+ parsedArgs . push ( fill , string ) ;
1367+ } else if ( args . length === 1 ) {
1368+ const string = exports . parseString ( args [ 0 ] ) ;
1369+ if ( ! string ) {
1370+ return null ;
1371+ }
1372+ parsedArgs . push ( string ) ;
1373+ }
1374+ return `path(${ parsedArgs . join ( ', ' ) } )` ;
1375+ }
1376+
1377+ /**
1378+ * polygon(<fill-rule>?, <length-percentage>{2}+)
1379+ */
1380+ if ( fn === 'polygon' ) {
1381+ const [ args ] = exports . splitTokens ( stringArgs , / , / ) ;
1382+ if ( args . length > 2 ) {
1383+ return null ;
1384+ }
1385+ if ( args . length === 2 ) {
1386+ const fill = exports . parseKeyword ( args . shift ( ) , [ 'evenodd' , 'nonzero' ] ) ;
1387+ if ( ! fill ) {
1388+ return null ;
1389+ }
1390+ parsedArgs . push ( fill ) ;
1391+ }
1392+ const [ vertices ] = exports . splitTokens ( args . shift ( ) ) ;
1393+ if (
1394+ vertices . length % 2 ||
1395+ ! vertices . every ( ( vertex , i ) => ( vertices [ i ] = exports . parseLengthOrPercentage ( vertex ) ) )
1396+ ) {
1397+ return null ;
1398+ }
1399+ parsedArgs . push ( vertices . join ( ' ' ) ) ;
1400+ return `polygon(${ parsedArgs . join ( ', ' ) } )` ;
1401+ }
1402+ }
1403+
1404+ return null ;
1405+ } ;
1406+
11891407/**
11901408 * @param {array } parts [horizontal radii, vertical radii]
11911409 * @returns {string }
0 commit comments