0544f93a262042a11df62f3b49f4869a9f43b1eb
[blender.git] / release / scripts / modules / bl_i18n_utils / utils_rtl.py
1 #!/usr/bin/python3
2
3 # ***** BEGIN GPL LICENSE BLOCK *****
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 #
19 # ***** END GPL LICENSE BLOCK *****
20
21 # <pep8 compliant>
22
23 # Preprocess right-to-left languages.
24 # You can use it either standalone, or through import_po_from_branches or
25 # update_trunk.
26 #
27 # Notes: This has been tested on Linux, not 100% it will work nicely on
28 #        Windows or OsX.
29 #        This uses ctypes, as there is no py3 binding for fribidi currently.
30 #        This implies you only need the compiled C library to run it.
31 #        Finally, note that it handles some formating/escape codes (like
32 #        \", %s, %x12, %.4f, etc.), protecting them from ugly (evil) fribidi,
33 #        which seems completely unaware of such things (as unicode is...).
34
35 import sys
36 import ctypes
37 import re
38
39
40 #define FRIBIDI_MASK_NEUTRAL    0x00000040L     /* Is neutral */
41 FRIBIDI_PAR_ON = 0x00000040
42
43
44 #define FRIBIDI_FLAG_SHAPE_MIRRORING    0x00000001
45 #define FRIBIDI_FLAG_REORDER_NSM        0x00000002
46
47 #define FRIBIDI_FLAG_SHAPE_ARAB_PRES    0x00000100
48 #define FRIBIDI_FLAG_SHAPE_ARAB_LIGA    0x00000200
49 #define FRIBIDI_FLAG_SHAPE_ARAB_CONSOLE 0x00000400
50
51 #define FRIBIDI_FLAG_REMOVE_BIDI        0x00010000
52 #define FRIBIDI_FLAG_REMOVE_JOINING     0x00020000
53 #define FRIBIDI_FLAG_REMOVE_SPECIALS    0x00040000
54
55 #define FRIBIDI_FLAGS_DEFAULT           ( \
56 #       FRIBIDI_FLAG_SHAPE_MIRRORING    | \
57 #       FRIBIDI_FLAG_REORDER_NSM        | \
58 #       FRIBIDI_FLAG_REMOVE_SPECIALS    )
59
60 #define FRIBIDI_FLAGS_ARABIC            ( \
61 #       FRIBIDI_FLAG_SHAPE_ARAB_PRES    | \
62 #       FRIBIDI_FLAG_SHAPE_ARAB_LIGA    )
63
64 FRIBIDI_FLAG_SHAPE_MIRRORING = 0x00000001
65 FRIBIDI_FLAG_REORDER_NSM = 0x00000002
66 FRIBIDI_FLAG_REMOVE_SPECIALS = 0x00040000
67
68 FRIBIDI_FLAG_SHAPE_ARAB_PRES = 0x00000100
69 FRIBIDI_FLAG_SHAPE_ARAB_LIGA = 0x00000200
70
71 FRIBIDI_FLAGS_DEFAULT = FRIBIDI_FLAG_SHAPE_MIRRORING | FRIBIDI_FLAG_REORDER_NSM | FRIBIDI_FLAG_REMOVE_SPECIALS
72
73 FRIBIDI_FLAGS_ARABIC = FRIBIDI_FLAG_SHAPE_ARAB_PRES | FRIBIDI_FLAG_SHAPE_ARAB_LIGA
74
75
76 MENU_DETECT_REGEX = re.compile("%x\\d+\\|")
77
78
79 ##### Kernel processing funcs. #####
80 def protect_format_seq(msg):
81     """
82     Find some specific escaping/formating sequences (like \", %s, etc.,
83     and protect them from any modification!
84     """
85 #    LRM = "\u200E"
86 #    RLM = "\u200F"
87     LRE = "\u202A"
88     RLE = "\u202B"
89     PDF = "\u202C"
90     LRO = "\u202D"
91     RLO = "\u202E"
92     uctrl = {LRE, RLE, PDF, LRO, RLO}
93     # Most likely incomplete, but seems to cover current needs.
94     format_codes = set("tslfd")
95     digits = set(".0123456789")
96
97     if not msg:
98         return msg
99     elif MENU_DETECT_REGEX.search(msg):
100         # An ugly "menu" message, just force it whole LRE if not yet done.
101         if msg[0] not in {LRE, LRO}:
102             msg = LRE + msg
103
104     idx = 0
105     ret = []
106     ln = len(msg)
107     while idx < ln:
108         dlt = 1
109 #        # If we find a control char, skip any additional protection!
110 #        if msg[idx] in uctrl:
111 #            ret.append(msg[idx:])
112 #            break
113         # \" or \'
114         if idx < (ln - 1) and msg[idx] == '\\' and msg[idx + 1] in "\"\'":
115             dlt = 2
116         # %x12|
117         elif idx < (ln - 2) and msg[idx] == '%' and msg[idx + 1] in "x" and msg[idx + 2] in digits:
118             dlt = 2
119             while (idx + dlt) < ln and msg[idx + dlt] in digits:
120                 dlt += 1
121             if (idx + dlt) < ln  and msg[idx + dlt] is '|':
122                 dlt += 1
123         # %.4f
124         elif idx < (ln - 3) and msg[idx] == '%' and msg[idx + 1] in digits:
125             dlt = 2
126             while (idx + dlt) < ln and msg[idx + dlt] in digits:
127                 dlt += 1
128             if (idx + dlt) < ln and msg[idx + dlt] in format_codes:
129                 dlt += 1
130             else:
131                 dlt = 1
132         # %s
133         elif idx < (ln - 1) and msg[idx] == '%' and msg[idx + 1] in format_codes:
134             dlt = 2
135
136         if dlt > 1:
137             ret.append(LRE)
138         ret += msg[idx:idx + dlt]
139         idx += dlt
140         if dlt > 1:
141             ret.append(PDF)
142
143     return "".join(ret)
144
145
146 def log2vis(msgs, settings):
147     """
148     Globally mimics deprecated fribidi_log2vis.
149     msgs should be an iterable of messages to rtl-process.
150     """
151     fbd = ctypes.CDLL(settings.FRIBIDI_LIB)
152
153     for msg in msgs:
154         msg = protect_format_seq(msg)
155
156         fbc_str = ctypes.create_unicode_buffer(msg)
157         ln = len(fbc_str) - 1
158 #        print(fbc_str.value, ln)
159         btypes = (ctypes.c_int * ln)()
160         embed_lvl = (ctypes.c_uint8 * ln)()
161         pbase_dir = ctypes.c_int(FRIBIDI_PAR_ON)
162         jtypes = (ctypes.c_uint8 * ln)()
163         flags = FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC
164
165         # Find out direction of each char.
166         fbd.fribidi_get_bidi_types(fbc_str, ln, ctypes.byref(btypes))
167
168 #        print(*btypes)
169
170         fbd.fribidi_get_par_embedding_levels(btypes, ln,
171                                              ctypes.byref(pbase_dir),
172                                              embed_lvl)
173
174 #        print(*embed_lvl)
175
176         # Joinings for arabic chars.
177         fbd.fribidi_get_joining_types(fbc_str, ln, jtypes)
178 #        print(*jtypes)
179         fbd.fribidi_join_arabic(btypes, ln, embed_lvl, jtypes)
180 #        print(*jtypes)
181
182         # Final Shaping!
183         fbd.fribidi_shape(flags, embed_lvl, ln, jtypes, fbc_str)
184
185 #        print(fbc_str.value)
186 #        print(*(ord(c) for c in fbc_str))
187         # And now, the reordering.
188         # Note that here, we expect a single line, so no need to do
189         # fancy things...
190         fbd.fribidi_reorder_line(flags, btypes, ln, 0, pbase_dir, embed_lvl,
191                                  fbc_str, None)
192 #        print(fbc_str.value)
193 #        print(*(ord(c) for c in fbc_str))
194
195         yield fbc_str.value