@@ -747,22 +747,57 @@ protected function processLine()
747747 $ this ->emit ('data ' , array ($ line ));
748748 }
749749
750- protected function strlen ($ str )
750+ private function strlen ($ str )
751751 {
752- return mb_strlen ($ str , $ this ->encoding );
752+ // prefer mb_strlen() if available
753+ if (function_exists ('mb_strlen ' )) {
754+ return mb_strlen ($ str , $ this ->encoding );
755+ }
756+
757+ // otherwise replace all unicode chars with dots and count dots
758+ return strlen (preg_replace ('/./us ' , '. ' , $ str ));
753759 }
754760
755- protected function substr ($ str , $ start = 0 , $ len = null )
761+ private function substr ($ str , $ start = 0 , $ len = null )
756762 {
757763 if ($ len === null ) {
758764 $ len = $ this ->strlen ($ str ) - $ start ;
759765 }
760- return (string )mb_substr ($ str , $ start , $ len , $ this ->encoding );
766+
767+ // prefer mb_substr() if available
768+ if (function_exists ('mb_substr ' )) {
769+ return (string )mb_substr ($ str , $ start , $ len , $ this ->encoding );
770+ }
771+
772+ // otherwise build array with all unicode chars and slice array
773+ preg_match_all ('/./us ' , $ str , $ matches );
774+
775+ return implode ('' , array_slice ($ matches [0 ], $ start , $ len ));
761776 }
762777
763- private function strwidth ($ str )
764- {
765- return mb_strwidth ($ str , $ this ->encoding );
778+ /** @internal */
779+ public function strwidth ($ str )
780+ {
781+ // prefer mb_strwidth() if available
782+ if (function_exists ('mb_strwidth ' )) {
783+ return mb_strwidth ($ str , $ this ->encoding );
784+ }
785+
786+ // otherwise replace each double-width unicode graphemes with two dots, all others with single dot and count number of dots
787+ // mbstring's list of double-width graphemes is *very* long: https://3v4l.org/GEg3u
788+ // let's use symfony's list from https://github.com/symfony/polyfill-mbstring/blob/e79d363049d1c2128f133a2667e4f4190904f7f4/Mbstring.php#L523
789+ // which looks like they originally came from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
790+ return strlen (preg_replace (
791+ array (
792+ '/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u ' ,
793+ '/./us ' ,
794+ ),
795+ array (
796+ '.. ' ,
797+ '. ' ,
798+ ),
799+ $ str
800+ ));
766801 }
767802
768803 /** @internal */
0 commit comments