2222/**
2323 * PhpianRender main class
2424 * Provides a unified interface for all RTL text processing features
25+ * Supports both instance and static method calls
2526 */
2627class PhpianRender
2728{
@@ -31,6 +32,11 @@ class PhpianRender
3132 private DiacriticsHandler $ diacriticsHandler ;
3233 private Helper $ helper ;
3334
35+ /**
36+ * Static instance for static method calls
37+ */
38+ private static ?PhpianRender $ staticInstance = null ;
39+
3440 /**
3541 * Constructor
3642 */
@@ -43,6 +49,19 @@ public function __construct()
4349 $ this ->helper = new Helper ();
4450 }
4551
52+ /**
53+ * Get or create static instance
54+ *
55+ * @return PhpianRender
56+ */
57+ private static function getStaticInstance (): PhpianRender
58+ {
59+ if (self ::$ staticInstance === null ) {
60+ self ::$ staticInstance = new self ();
61+ }
62+ return self ::$ staticInstance ;
63+ }
64+
4665 /**
4766 * Process text with all RTL features
4867 *
@@ -59,6 +78,7 @@ public function process(string $text, array $options = []): string
5978 'numberLocale ' => 'persian ' , // 'persian' or 'arabic'
6079 'preserveDiacritics ' => true ,
6180 'clean ' => false ,
81+ 'reverse ' => true , // Reverse text for RTL display (like PersianRender)
6282 ];
6383
6484 $ options = array_merge ($ defaultOptions , $ options );
@@ -77,6 +97,9 @@ public function process(string $text, array $options = []): string
7797 $ diacritics = $ extracted ['diacritics ' ];
7898 }
7999
100+ // Check if text is mixed (RTL + LTR) or pure RTL
101+ $ isMixed = $ this ->isMixedText ($ text );
102+
80103 // Reshape characters
81104 $ reshapedText = $ text ;
82105 if ($ options ['reshape ' ]) {
@@ -94,8 +117,17 @@ public function process(string $text, array $options = []): string
94117 }
95118
96119 // Process bidirectional text
120+ // For mixed texts (RTL + LTR), use BiDi algorithm
121+ // For pure RTL texts, just reverse the entire text (like PersianRender)
97122 if ($ options ['bidi ' ]) {
98- $ text = $ this ->bidi ->process ($ text );
123+ if ($ isMixed ) {
124+ // Mixed text: use BiDi algorithm
125+ $ text = $ this ->bidi ->process ($ text );
126+ } elseif ($ options ['reverse ' ] && $ this ->helper ->isRTL ($ text )) {
127+ // Pure RTL text: reverse entire text (like PersianRender.php)
128+ $ chars = mb_str_split ($ text , 1 , 'UTF-8 ' );
129+ $ text = implode ('' , array_reverse ($ chars ));
130+ }
99131 }
100132
101133 // Convert numbers
@@ -109,6 +141,64 @@ public function process(string $text, array $options = []): string
109141 return $ text ;
110142 }
111143
144+ /**
145+ * Check if text contains both RTL and LTR characters
146+ *
147+ * @param string $text Input text
148+ * @return bool True if text is mixed
149+ */
150+ private function isMixedText (string $ text ): bool
151+ {
152+ $ hasRTL = false ;
153+ $ hasLTR = false ;
154+ $ chars = mb_str_split ($ text , 1 , 'UTF-8 ' );
155+
156+ foreach ($ chars as $ char ) {
157+ if (ctype_space ($ char ) || ctype_punct ($ char )) {
158+ continue ;
159+ }
160+
161+ // Check if it's RTL
162+ $ codePoint = $ this ->getCharCodePoint ($ char );
163+ if (($ codePoint >= 0x0590 && $ codePoint <= 0x08FF ) ||
164+ ($ codePoint >= 0x0600 && $ codePoint <= 0x06FF ) ||
165+ in_array ($ codePoint , [0x067E , 0x0686 , 0x06AF , 0x0698 ])) {
166+ $ hasRTL = true ;
167+ } else {
168+ // Check if it's LTR (Latin characters or numbers)
169+ if (($ codePoint >= 0x0041 && $ codePoint <= 0x007A ) ||
170+ ($ codePoint >= 0x0030 && $ codePoint <= 0x0039 )) {
171+ $ hasLTR = true ;
172+ }
173+ }
174+
175+ if ($ hasRTL && $ hasLTR ) {
176+ return true ;
177+ }
178+ }
179+
180+ return false ;
181+ }
182+
183+ /**
184+ * Get Unicode code point of a character
185+ *
186+ * @param string $char Character
187+ * @return int Code point
188+ */
189+ private function getCharCodePoint (string $ char ): int
190+ {
191+ $ bytes = unpack ('C* ' , mb_convert_encoding ($ char , 'UTF-32BE ' , 'UTF-8 ' ));
192+ if (empty ($ bytes )) {
193+ return 0 ;
194+ }
195+ $ codePoint = 0 ;
196+ foreach ($ bytes as $ byte ) {
197+ $ codePoint = ($ codePoint << 8 ) | $ byte ;
198+ }
199+ return $ codePoint ;
200+ }
201+
112202 /**
113203 * Reshape text only
114204 *
@@ -217,5 +307,88 @@ public function getHelper(): Helper
217307 {
218308 return $ this ->helper ;
219309 }
310+
311+ /**
312+ * Get package version
313+ *
314+ * @return string Package version
315+ */
316+ public static function getVersion (): string
317+ {
318+ return Version::getVersion ();
319+ }
320+
321+ // ========== Static Methods ==========
322+
323+ /**
324+ * Process text with all RTL features (static)
325+ *
326+ * @param string $text Input text
327+ * @param array $options Processing options
328+ * @return string Processed text
329+ */
330+ public static function processStatic (string $ text , array $ options = []): string
331+ {
332+ return self ::getStaticInstance ()->process ($ text , $ options );
333+ }
334+
335+ /**
336+ * Reshape text only (static)
337+ *
338+ * @param string $text Input text
339+ * @return string Reshaped text
340+ */
341+ public static function reshapeStatic (string $ text ): string
342+ {
343+ return self ::getStaticInstance ()->reshape ($ text );
344+ }
345+
346+ /**
347+ * Process bidirectional text only (static)
348+ *
349+ * @param string $text Input text
350+ * @return string Processed text
351+ */
352+ public static function processBiDiStatic (string $ text ): string
353+ {
354+ return self ::getStaticInstance ()->processBiDi ($ text );
355+ }
356+
357+ /**
358+ * Convert numbers (static)
359+ *
360+ * @param string $text Input text
361+ * @param string $locale Target locale ('persian' or 'arabic')
362+ * @return string Text with converted numbers
363+ */
364+ public static function convertNumbersStatic (string $ text , string $ locale = 'persian ' ): string
365+ {
366+ return self ::getStaticInstance ()->convertNumbers ($ text , $ locale );
367+ }
368+
369+ /**
370+ * Word wrap for RTL text (static)
371+ *
372+ * @param string $text Text to wrap
373+ * @param int $width Maximum line width
374+ * @param string $break Line break character
375+ * @param bool $cut Whether to cut words
376+ * @return string Wrapped text
377+ */
378+ public static function wordWrapStatic (string $ text , int $ width = 75 , string $ break = "\n" , bool $ cut = false ): string
379+ {
380+ return self ::getStaticInstance ()->wordWrap ($ text , $ width , $ break , $ cut );
381+ }
382+
383+ /**
384+ * Check if text is RTL (static)
385+ *
386+ * @param string $text Text to check
387+ * @return bool True if RTL
388+ */
389+ public static function isRTLStatic (string $ text ): bool
390+ {
391+ return self ::getStaticInstance ()->isRTL ($ text );
392+ }
220393}
221394
0 commit comments