@@ -69,6 +69,28 @@ function wp_tinycolor_bound01( $n, $max ) {
6969 return ( $ n % $ max ) / (float ) $ max ;
7070}
7171
72+ /**
73+ * Direct port of tinycolor's boundAlpha function to maintain consistency with
74+ * how tinycolor works.
75+ *
76+ * @see https://github.com/bgrins/TinyColor
77+ *
78+ * @since 5.9.0
79+ * @access private
80+ *
81+ * @param mixed $n Number of unknown type.
82+ * @return float Value in the range [0,1].
83+ */
84+ function _wp_tinycolor_bound_alpha ( $ n ) {
85+ if ( is_numeric ( $ n ) ) {
86+ $ n = (float ) $ n ;
87+ if ( $ n >= 0 && $ n <= 1 ) {
88+ return $ n ;
89+ }
90+ }
91+ return 1 ;
92+ }
93+
7294/**
7395 * Round and convert values of an RGB object.
7496 *
@@ -170,8 +192,7 @@ function wp_tinycolor_hsl_to_rgb( $hsl_color ) {
170192
171193/**
172194 * Parses hex, hsl, and rgb CSS strings using the same regex as TinyColor v1.4.2
173- * used in the JavaScript. Only colors output from react-color are implemented
174- * and the alpha value is ignored as it is not used in duotone.
195+ * used in the JavaScript. Only colors output from react-color are implemented.
175196 *
176197 * Direct port of TinyColor's function, lightly simplified to maintain
177198 * consistency with TinyColor.
@@ -180,6 +201,7 @@ function wp_tinycolor_hsl_to_rgb( $hsl_color ) {
180201 * @see https://github.com/casesandberg/react-color/
181202 *
182203 * @since 5.8.0
204+ * @since 5.9.0 Added alpha processing.
183205 * @access private
184206 *
185207 * @param string $color_str CSS color string.
@@ -199,35 +221,47 @@ function wp_tinycolor_string_to_rgb( $color_str ) {
199221
200222 $ rgb_regexp = '/^rgb ' . $ permissive_match3 . '$/ ' ;
201223 if ( preg_match ( $ rgb_regexp , $ color_str , $ match ) ) {
202- return wp_tinycolor_rgb_to_rgb (
224+ $ rgb = wp_tinycolor_rgb_to_rgb (
203225 array (
204226 'r ' => $ match [1 ],
205227 'g ' => $ match [2 ],
206228 'b ' => $ match [3 ],
207229 )
208230 );
231+
232+ $ rgb ['a ' ] = 1 ;
233+
234+ return $ rgb ;
209235 }
210236
211237 $ rgba_regexp = '/^rgba ' . $ permissive_match4 . '$/ ' ;
212238 if ( preg_match ( $ rgba_regexp , $ color_str , $ match ) ) {
213- return wp_tinycolor_rgb_to_rgb (
239+ $ rgb = wp_tinycolor_rgb_to_rgb (
214240 array (
215241 'r ' => $ match [1 ],
216242 'g ' => $ match [2 ],
217243 'b ' => $ match [3 ],
218244 )
219245 );
246+
247+ $ rgb ['a ' ] = _wp_tinycolor_bound_alpha ( $ match [4 ] );
248+
249+ return $ rgb ;
220250 }
221251
222252 $ hsl_regexp = '/^hsl ' . $ permissive_match3 . '$/ ' ;
223253 if ( preg_match ( $ hsl_regexp , $ color_str , $ match ) ) {
224- return wp_tinycolor_hsl_to_rgb (
254+ $ rgb = wp_tinycolor_hsl_to_rgb (
225255 array (
226256 'h ' => $ match [1 ],
227257 's ' => $ match [2 ],
228258 'l ' => $ match [3 ],
229259 )
230260 );
261+
262+ $ rgb ['a ' ] = 1 ;
263+
264+ return $ rgb ;
231265 }
232266
233267 $ hsla_regexp = '/^hsla ' . $ permissive_match4 . '$/ ' ;
@@ -239,50 +273,87 @@ function wp_tinycolor_string_to_rgb( $color_str ) {
239273 'l ' => $ match [3 ],
240274 )
241275 );
276+
277+ $ rgb ['a ' ] = _wp_tinycolor_bound_alpha ( $ match [4 ] );
278+
279+ return $ rgb ;
242280 }
243281
244282 $ hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ ' ;
245283 if ( preg_match ( $ hex8_regexp , $ color_str , $ match ) ) {
246- return wp_tinycolor_rgb_to_rgb (
284+ $ rgb = wp_tinycolor_rgb_to_rgb (
247285 array (
248286 'r ' => base_convert ( $ match [1 ], 16 , 10 ),
249287 'g ' => base_convert ( $ match [2 ], 16 , 10 ),
250288 'b ' => base_convert ( $ match [3 ], 16 , 10 ),
251289 )
252290 );
291+
292+ $ rgb ['a ' ] = _wp_tinycolor_bound_alpha (
293+ base_convert ( $ match [4 ], 16 , 10 ) / 255
294+ );
295+
296+ return $ rgb ;
253297 }
254298
255299 $ hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ ' ;
256300 if ( preg_match ( $ hex6_regexp , $ color_str , $ match ) ) {
257- return wp_tinycolor_rgb_to_rgb (
301+ $ rgb = wp_tinycolor_rgb_to_rgb (
258302 array (
259303 'r ' => base_convert ( $ match [1 ], 16 , 10 ),
260304 'g ' => base_convert ( $ match [2 ], 16 , 10 ),
261305 'b ' => base_convert ( $ match [3 ], 16 , 10 ),
262306 )
263307 );
308+
309+ $ rgb ['a ' ] = 1 ;
310+
311+ return $ rgb ;
264312 }
265313
266314 $ hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/ ' ;
267315 if ( preg_match ( $ hex4_regexp , $ color_str , $ match ) ) {
268- return wp_tinycolor_rgb_to_rgb (
316+ $ rgb = wp_tinycolor_rgb_to_rgb (
269317 array (
270318 'r ' => base_convert ( $ match [1 ] . $ match [1 ], 16 , 10 ),
271319 'g ' => base_convert ( $ match [2 ] . $ match [2 ], 16 , 10 ),
272320 'b ' => base_convert ( $ match [3 ] . $ match [3 ], 16 , 10 ),
273321 )
274322 );
323+
324+ $ rgb ['a ' ] = _wp_tinycolor_bound_alpha (
325+ base_convert ( $ match [4 ] . $ match [4 ], 16 , 10 ) / 255
326+ );
327+
328+ return $ rgb ;
275329 }
276330
277331 $ hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/ ' ;
278332 if ( preg_match ( $ hex3_regexp , $ color_str , $ match ) ) {
279- return wp_tinycolor_rgb_to_rgb (
333+ $ rgb = wp_tinycolor_rgb_to_rgb (
280334 array (
281335 'r ' => base_convert ( $ match [1 ] . $ match [1 ], 16 , 10 ),
282336 'g ' => base_convert ( $ match [2 ] . $ match [2 ], 16 , 10 ),
283337 'b ' => base_convert ( $ match [3 ] . $ match [3 ], 16 , 10 ),
284338 )
285339 );
340+
341+ $ rgb ['a ' ] = 1 ;
342+
343+ return $ rgb ;
344+ }
345+
346+ /*
347+ * The JS color picker considers the string "transparent" to be a hex value,
348+ * so we need to handle it here as a special case.
349+ */
350+ if ( 'transparent ' === $ color_str ) {
351+ return array (
352+ 'r ' => 0 ,
353+ 'g ' => 0 ,
354+ 'b ' => 0 ,
355+ 'a ' => 0 ,
356+ );
286357 }
287358}
288359
@@ -313,6 +384,95 @@ function wp_register_duotone_support( $block_type ) {
313384 }
314385 }
315386}
387+ /**
388+ * Renders the duotone filter SVG and returns the CSS filter property to
389+ * reference the rendered SVG.
390+ *
391+ * @since 5.9.0
392+ *
393+ * @param array $preset Duotone preset value as seen in theme.json.
394+ * @return string Duotone CSS filter property.
395+ */
396+ function wp_render_duotone_filter_preset ( $ preset ) {
397+ $ duotone_id = $ preset ['slug ' ];
398+ $ duotone_colors = $ preset ['colors ' ];
399+ $ filter_id = 'wp-duotone- ' . $ duotone_id ;
400+ $ duotone_values = array (
401+ 'r ' => array (),
402+ 'g ' => array (),
403+ 'b ' => array (),
404+ 'a ' => array (),
405+ );
406+ foreach ( $ duotone_colors as $ color_str ) {
407+ $ color = wp_tinycolor_string_to_rgb ( $ color_str );
408+
409+ $ duotone_values ['r ' ][] = $ color ['r ' ] / 255 ;
410+ $ duotone_values ['g ' ][] = $ color ['g ' ] / 255 ;
411+ $ duotone_values ['b ' ][] = $ color ['b ' ] / 255 ;
412+ $ duotone_values ['a ' ][] = $ color ['a ' ];
413+ }
414+
415+ ob_start ();
416+
417+ ?>
418+
419+ <svg
420+ xmlns="http://www.w3.org/2000/svg"
421+ viewBox="0 0 0 0"
422+ width="0"
423+ height="0"
424+ focusable="false"
425+ role="none"
426+ style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;"
427+ >
428+ <defs>
429+ <filter id="<?php echo esc_attr ( $ filter_id ); ?> ">
430+ <feColorMatrix
431+ color-interpolation-filters="sRGB"
432+ type="matrix"
433+ values="
434+ .299 .587 .114 0 0
435+ .299 .587 .114 0 0
436+ .299 .587 .114 0 0
437+ .299 .587 .114 0 0
438+ "
439+ />
440+ <feComponentTransfer color-interpolation-filters="sRGB" >
441+ <feFuncR type="table" tableValues="<?php echo esc_attr ( implode ( ' ' , $ duotone_values ['r ' ] ) ); ?> " />
442+ <feFuncG type="table" tableValues="<?php echo esc_attr ( implode ( ' ' , $ duotone_values ['g ' ] ) ); ?> " />
443+ <feFuncB type="table" tableValues="<?php echo esc_attr ( implode ( ' ' , $ duotone_values ['b ' ] ) ); ?> " />
444+ <feFuncA type="table" tableValues="<?php echo esc_attr ( implode ( ' ' , $ duotone_values ['a ' ] ) ); ?> " />
445+ </feComponentTransfer>
446+ <feComposite in2="SourceGraphic" operator="in" />
447+ </filter>
448+ </defs>
449+ </svg>
450+
451+ <?php
452+
453+ $ svg = ob_get_clean ();
454+
455+ if ( ! defined ( 'SCRIPT_DEBUG ' ) || ! SCRIPT_DEBUG ) {
456+ // Clean up the whitespace.
457+ $ svg = preg_replace ( "/[ \r\n\t ]+/ " , ' ' , $ svg );
458+ $ svg = preg_replace ( '/> </ ' , '>< ' , $ svg );
459+ $ svg = trim ( $ svg );
460+ }
461+
462+ add_action (
463+ /*
464+ * Safari doesn't render SVG filters defined in data URIs,
465+ * and SVG filters won't render in the head of a document,
466+ * so the next best place to put the SVG is in the footer.
467+ */
468+ is_admin () ? 'admin_footer ' : 'wp_footer ' ,
469+ static function () use ( $ svg ) {
470+ echo $ svg ;
471+ }
472+ );
473+
474+ return "url('# " . $ filter_id . "') " ;
475+ }
316476
317477/**
318478 * Render out the duotone stylesheet and SVG.
0 commit comments