
Once in the next version of ABBYY FineReader Sprint (a text recognition program that comes with an MFP and scanners), it was necessary to add the Arabic interface language. And wrap it all up ...
Usually, localization to a “new” language is a simple matter (for development): start a constant or another, tune the build system, and, in general, everything. The rest falls on the shoulders of technicians and translators. But in Arabic they write from right to left, and this entails many changes in the interface. I will tell about this experience in the article.
Expand all (actually not)
Long before the first translation of the Arabic language came from the translation, I took up the RTL question (right-to-left: alas, I did not come up with a good Russian translation of this term) interface. The fact is that the layout of the child windows follows the direction of writing: the “cross” of the window closes and the scrollers are pressed to the left, and the main menu is right, even the progress bars run from right to left. In general, everything is upside down. The easiest way to achieve this behavior is to insert such a call into the initialization code (before creating the first window):
')
SetProcessDefaultLayout (LAYOUT_RTL);
After that, all top-level windows created in the current process will be created with the WS_EX_LAYOUTRTL style. This style will also be inherited and all child windows (not quite everything, but as a first approximation will come down). By the way, not the easiest way to achieve a mirrored behavior is to affix WS_EX_LAYOUTRTL to all top-level windows upon creation. In any case, it will turn out like this:

Immediately you can see many problems that you will have to overcome: mirrored icons on toolbars (1) and buttons (2), expanded text (3), unsuitable background under the title Tasks (4), punctuation that went the wrong way (5) and t .P.
Actually, the WS_EX_LAYOUTRTL style expands the client coordinates (and those used in WM_NCPAINT). Now they will have the beginning of the upper right point, and the abscissa will increase from right to left. Also, all device contexts (HDC) associated with this window will be LAYOUT_RTL (a brief description
here ).
Transform Window Coordinates
In the RTL world of Windows, only
client coordinates are deployed. Window (desktop) remain familiar LTR directionality. And the first victims in this situation are the ClientToScreen and ScreenToClient methods (the Remarks section in this
article ). Since we use these methods indirectly, it was enough to teach our wrappers how to handle RTL situations. I sometimes have fun searching for reasons why the Windows developers couldn’t do the same. By the way, the MSDN recommendation is to use the MapWindowPoints method. In the newly written code, I did, as it is sometimes more convenient.
Much more unpleasant (because it is not detected by a simple search on the project) may be mixing in the calculations of window and client coordinates. An example is
here , in the Mapping Coordinates section. Fortunately, we didn’t have such a thing in the project (I looked at all the suspicious places, and testing didn’t reveal any problems). What can I say: just don't do it.
A sad story about why the text was mirrored
To combat flicker, we use the standard buffering method: all drawing is done in BITMAP in memory, and at the end it is simply copied to the device context using BitBlt. The roots of the problem were in the wrapper we used over a pair of the CreateCompatibleDC and SelectObject methods (/*..*/, bitmap), namely, in this class with a constructor with a default parameter:
explicit CBitmapDC (HBITMAP bitmap, HDC compatibleWith = 0);
In the code, this constructor was used everywhere, and it was with the default parameter. As a result, the resulting device context was compatible with the desktop (which, like the dextup coordinates, remains LTR), but not with the window in which we painted. Never liked the default settings. After correcting this misunderstanding, it became much better:

By the way, despite the fact that I did not change the positioning flags of the text (c DT_LEFT), the text is pressed to the right. It just works, which is nice.
Drawing pictures. Why did they turn around
As can be seen from the previous screenshot, the problem with mirroring the image remained unresolved. In truth, it would be better to call this section "Image List - how not to go crazy." The fact is that all the problematic graphics in the product is presented in the form of HIMAGELIST. And the problem here is not one.
To ensure that the icon from the image list does not expand when drawing, MSDN recommends using the ILC_MIRROR and ILC_PERITEMMIRROR flags when creating the image list. Beginning with Windows XP (our system requirements indicate the minimum supported version of Window XP SP3), I did not find any differences between how the ILC_MIRROR flag works and how the combination of these flags works. In any case, the addition of this flag almost did not solve our problems.
Almost all the graphics in the product exist in two versions: 8-bit in the system palette and 32-bit with alpha channel. This is done for normal operation in low-color modes for those who need it, while maintaining the "beauty" of all others. And the above method of dealing with mirroring worked only with 8-bit icons. That's because we use not the system method ImageList_Draw, but our own implementation for drawing translucent icons — we draw a device context representation directly in the DIB. By the way, the DIB doesn't know anything about RTL, and the pixels in it are indexed as usual - from left to right. Therefore, it would seem that there should be no problems. They would not exist if, in preparation for the future RTL, the team responsible for the shared library did not add the notorious mirroring to the code. As far as I understand, the reason was that at that time there was no understanding how to work with graphics in RTL (MSDN as one of the solutions to this problem suggests to mirror the graphics in the image editor so that it will be displayed correctly after re-mirroring) . Well, I will say the opinion of the author of our graphics - it is much more convenient to work without this double mirroring. Because there is a third problem with graphics.
Do I need to have separate graphics for RTL?
In general, the answer is: "Yes, you need." Let's go back to the same screenshot. It can be noted that, despite the inverted W, the icon has one important advantage: the arrow looks in the right (for RTL) direction. Yes, for Arabs (and Israelis), the “forward” direction will be to the left, not to the right. Here, for example, Internet Explorer in Windows with English (left) and Arabic (right) UI for comparison:

In addition to arrows, asymmetrical punctuation marks, if they are in the picture, should also be mirrored. In most cases this applies only to the question mark.

And, by the way, a separate graphic (or mirroring when drawing) is also required for the background of the Tasks header on the first screenshot.
And what about the dialogs and other pop-up windows?
As I mentioned, the call to SetProcessDefaultLayout is almost enough for the windows to be turned. But in addition to the top-level windows (that is, those WS_POPUP windows that have 0 as their ancestor), there are other pop-up windows. For example, dialogue. They do not get the WS_EX_LAYOUTRTL machine. But everything is simple - you just need to specify this style in the resources, or, if the pop-up (WS_POPUP) window is not created from resources, add this flag when you create it. For example, under the condition that such a style is present in the parent window. I sometimes wonder why Microsoft stopped halfway there?
The fact is that we do not always manage the creation window flags. And if in the case of property sheets and wizards, everything is simple - the direction of the layout is determined by the first page added, then with message windows and standard dialogs everything is more complicated.
First, about the message boxes that are shown by the MessageBox method. For their reversal, it is enough to use a combination of flags MB_RIGHT | MB_RTLREADING. This is how simple it is. Well, of course, simple, if you use the system call not directly, but through a wrapper. Because otherwise, you will have to add these flags to each MessageBox call (and, if Arabic and Hebrew are not the only localization languages, do it by a certain condition).
With standard dialogues, everything is both more complicated and simpler. Easier, because their layout depends only on the localization of the operating system. More difficult - for the same reason. If for some reason it is important that the entire application was RTL, then in this place you cannot do without crutches. By the way, the same applies to the "new" file dialogs (IFileDialog). In our case, we left it as it is, considering the problem unimportant.
What to do if the child window (WS_CHILD) does not need the WS_EX_LAYOUTRTL flag
As I said, child windows inherit this flag from the parent. But sometimes it is not necessary (for example, in ABBYY FineReader Sprint is an image editor window - there is no point in mirroring the output of pictures, and, moreover, the selection frame for clipping tools, pagination, etc.). There are two ways - remove this flag immediately after creation or use the WS_EX_NOINHERITLAYOUT flag when creating the parent window. The first way seems more reasonable in most cases.
By the way, as I mentioned, not all windows inherit the WS_EX_LAYOUTRTL style from the parent. Many standard controls (common controls) replace it with others (to behave in RTL ways). For example, for edit, static it will be a combination of WS_EX_RIGHT flags | WS_EX_RTLREADING.
This is the reason for the most unusual mistake I encountered. In the language selection box, there is a hint feature as you type:

As you can see, a tooltip pops up at the caret position. I get the position of the carriage by the method GetCaretPos, which returns it in client coordinates. Then I receive (using MapWindowPoints) window coordinates of the carriage, put the hint window in them, and, in the case of RTL, I see that the hint appears in the opposite edge of the combo box:

The reason for this error is that the client coordinates in which I get the position of the carriage are the coordinates of not the combo box, but a child of the edit control for it. Which, as I said above, does not inherit (as opposed to combo box) flag WS_EX_LAYOUTRTL. The fix, respectively, is obvious: just change the window in the MapWindowPoints call to the correct one.
And about drawing. Now gdi +
MSDN does not recommend using GDI + in an RTL environment. Namely, GDI + methods do not take into account the LAYOUT_RTL property of the device context on which they draw. However, you can try it - you just have to convert the coordinates yourself when drawing. The most convenient way was the following: using the device context (HDC) with the LAYOUT_RTL property set, call the LPToDP method to translate the coordinates.
However, the drawing code on GDI + turned out to be somewhat confusing after these edits, so I found a way to do without GDI + in this product at all.
Mikhail Vasilchenko,
text recognition department