Arabic localization: windows and drawing

  • Tutorial
Once in the next version of ABBYY FineReader Sprint (a text recognition program that comes with MFPs and scanners), it was necessary to add an Arabic interface language. And wrap it all up ...

Usually localization into a "new" language is a simple matter (for development): to get a constant or two, to tune the build system, and, in general, that's all. The rest falls on the shoulders of technical and translators. But in Arabic they write from right to left, and this entails many changes in the interface. I will talk about this experience in the article.

Expand everything (actually not)

Long before the first lines translated into Arabic came from the translation, I dealt with the RTL (right-to-left: alas, I did not come up with a good Russian translation of this term) interface. The fact is that the location of the daughter windows follows the direction of writing: the “cross” of closing the window and the scrollers are pressed to the left, and the main menu is to the right, even progress bars run from right to left. In general, everything is upside down. The easiest way to achieve this behavior is to insert the following call into the initialization code (before creating the first window):

SetProcessDefaultLayout (LAYOUT_RTL);

After that, all the top-level windows created in the current process will be created with the WS_EX_LAYOUTRTL style. This style will also be inherited by all child windows (not quite all, but as a first approximation it will do). By the way, not the easiest way to achieve mirrored behavior is to set WS_EX_LAYOUTRTL to all top-level windows when creating. In any case, it will turn out something like this:

Immediately visible are many problems that you will have to deal with: mirrored icons on the toolbars (1) and buttons (2), expanded text (3), a non-matching background under the Tasks (4) heading, Punctuation (5), flying off the wrong way, etc.

Actually, the WS_EX_LAYOUTRTL style expands 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 (short description here ).

Convert window coordinates

In the RTL world of Windows, only client coordinates are deployed . Window (desktop) remain the usual LTR orientation. And the first victims in this situation are the ClientToScreen and ScreenToClient methods (Remarks section in this article ). Since we do not use these methods directly, it turned out to be sufficient to teach our wrappers how to handle the RTL situation. I sometimes have fun finding reasons why 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 so, 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 calculation of window and client coordinates. Example herehere in the Mapping Coordinates section. Fortunately, this was not the case in our project (I looked at all the suspicious places, and testing did not reveal any problems). What can I say: just don't do it.

The sad story of why the text was mirrored

To combat flicker, we use the standard buffering method: all drawing is done in BITMAP in memory, and in 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 couple of CreateCompatibleDC and SelectObject (/*..*/, bitmap) methods, namely in this class with the default constructor:

explicit CBitmapDC (HBITMAP bitmap, HDC compatibleWith = 0);

This constructor was used everywhere in the code, and this is exactly what happened with the default parameter. As a result, the resulting device context was compatible with the desktop (which, like the desktop coordinates, remains LTR), but not with the window in which we painted. Never liked the default options. After correcting this misunderstanding, it became much better:

By the way, despite the fact that I did not change the flags for positioning the text (with DT_LEFT), the text is pressed to the right. It just works, which is nice.

Drawing pictures. Why did they turn around

As you can see from the previous screenshot, the problem with mirroring the image remained unresolved. In truth, it would be more correct to call this section “Image List - how not to lose your mind”. The fact is that all the problematic graphics in the product are presented in the form of HIMAGELIST. And the problem here is far from one.

To prevent the icon from the image list from expanding during drawing, MSDN recommends using the ILC_MIRROR and ILC_PERITEMMIRROR flags when creating the image list. Starting with Windows XP (the minimum supported version of Window XP SP3 is specified in our system requirements), I did not find any differences between how the ILC_MIRROR flag works and how the union of these flags works. In any case, adding this flag almost did not solve our problems.

Almost all graphics in the product exist in two versions: 8-bit in the system palette and 32-bit with the alpha channel. This is done for normal operation in low-color modes for those who need it, while maintaining the "beauty" of everyone else. And the above method of dealing with mirroring worked only with 8-bit icons. And all because we do not use the system method ImageList_Draw to draw translucent icons, but our own implementation - we draw directly the device context representation in the DIB. By the way, the DIB does not 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 have happened if, in preparation for the future RTL, the team responsible for the shared library had not added the notorious mirroring to the drawing code. As I understand it, 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 mirroring the graphics in the image editor so that it is displayed correctly after repeated 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 the graphics.

Do I need to have separate graphics for RTL?

In the general case, the answer is "Yes, I need to." Let's go back to the same screenshot. You may notice 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 direction "forward" will be to the left, and not to the right. Here, for example, Internet Explorer on Windows with English (left) and Arabic (right) UI for comparison:

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

And, by the way, a separate graphic (or mirroring when drawing) is required for the background of the Tasks header in the first screenshot.

What about dialogs and other pop-ups?

As I mentioned, calling SetProcessDefaultLayout is almost enough for windows to expand. But in addition to top-level windows (that is, those WS_POPUP windows that specify 0 as their ancestor), there are other pop-up windows. For example, dialogs. They do not automatically receive WS_EX_LAYOUTRTL. But everything is simple here - 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 creating. For example, under the condition that such a style is present in the parent window. I sometimes wonder why Microsoft stopped halfway there too?

The fact is that we do not always control the window flags of creation. And if in the case of property pages and wizards, everything is simple - there the direction of the layout is determined by the first page added, then with message windows and standard dialogs it’s more complicated.

First, about the message boxes that are displayed by the MessageBox method. To reverse them, just use the combination of flags MB_RIGHT | MB_RTLREADING. Just like that. Well, of course, it's simple if you use a system call not directly, but through a wrapper. Because otherwise you will have to add these flags to every MessageBox call (and, if Arabic and Hebrew are not the only localization languages, do this by a certain condition).

With standard dialogs, 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 is RTL, then crutches can not do without this. By the way, the same applies to the "new" file dialogs (IFileDialog). In our case, we left this as is, considering the problem insignificant.

What to do if the child window (WS_CHILD) does not need the WS_EX_LAYOUTRTL flag

As I said before, child windows inherit this flag from the parent. But sometimes this is not necessary (for example, in ABBYY FineReader Sprint this is an image editor window - it makes no sense to mirror the output of images, and, especially, the selection frame for cropping, paging, etc.). There are two ways - to clear 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 already mentioned, not all windows inherit the WS_EX_LAYOUTRTL style from their parents. Many common controls replace it with others (to behave in an RTL way). For example, for edit, static it will be a combination of flags WS_EX_RIGHT | WS_EX_RTLREADING.

It is with this that the most unusual mistake that I met is associated. In the language selection combo box, there is a feature of the tooltip as you type:

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

The reason for this error is that the client coordinates in which I get the carriage position are not the coordinates of the combo box, but the edit control of its child. Which, as I said above, does not inherit (unlike the combo box) the WS_EX_LAYOUTRTL flag. The fix, respectively, is obvious: just change the window in the MapWindowPoints call to the correct one.

And more about drawing. Now gdi +

MSDN does not recommend the use of GDI + in RTL environments. Namely, the GDI + methods do not take into account the LAYOUT_RTL property of the device context on which they are drawn. Nevertheless, you can try it - you just have to convert the coordinates yourself when drawing. The following method was most convenient: using the device context (HDC) with the LAYOUT_RTL property set, call the LPToDP method to transfer coordinates.
However, the drawing code on GDI + turned out to be somewhat confusing after these edits, so I found a way to get by with this product without GDI + at all.

Mikhail Vasilchenko,
text recognition products department

Also popular now: