ondras / rot.js

ROguelike Toolkit in JavaScript. Cool dungeon-related stuff, interactive manual, documentation, tests!
https://ondras.github.io/rot.js/hp/
BSD 3-Clause "New" or "Revised" License
2.34k stars 255 forks source link

Support for right-to-left languages #32

Open thesnarky1 opened 10 years ago

thesnarky1 commented 10 years ago

ROT works nicely for languages that are written left-to-right in a non-connecting script. It handles Unicode just fine (as long as the browser does) so that covers a lot of ground. However, two areas that I believe would open this up to a much broader global audience would be support for a native right-to-left rendering and the ability to nicely display characters from languages that use cursive scripts (such as Arabic, Farsi, Hindi, etc). This ticket is for right-to-left rendering to keep the issues able to be separately pulled.

Issue

The issue is that if an author wants to include text from a non-left-to-right language, ROT does not work. The author has to reverse the text before displaying, as well as do all their computations for layout based on where the end of the word should fall, instead of where it should start.

I have some ideas for rogue-likes based in the Middle East which cannot be implemented using ROT without support for right-to-left languages. There are possible work arounds, such as simply flipping every String I'd want to display, but that would make internationalization a lot trickier.

Proposed Solution

One solution would be to mess with ROT.Display.drawText to reverse a string if passed a particular boolean, or have a ROT.Display.drawReversedString method. I do not believe this is optimal because it will complicate a rogue-like author's work for figuring out text placement and layout. It also requires the author to call the right functions based on the language to display, instead of relying on code that reads the same regardless (which is what one would want for an internationalized game). The upside to this solution would be that an author can include "flavor text" in a game containing a different script without having to create an entirely new canvas.

Due to ROT's design, I believe this can be solved relatively easily by adding a variable (rightToLeft) to the ROT.Display.options hash and checking that in the relative drawWithCache or drawNoCache functions. What we'll do is to draw the X coordinate backwards, starting at the width and subtracting however far we should go.

ROT.Display.Rect.prototype._drawNoCache = function(data, clearBefore) {
    var x = data[0];
    var y = data[1];
    var ch = data[2];
    var fg = data[3];
    var bg = data[4];

    if (clearBefore) {
        var b = this._options.border;
        this._context.fillStyle = bg;
        this._context.fillRect(x*this._spacingX + b, y*this._spacingY + b, this._spacingX - b, this._spacingY - b);
    }

    if (!ch) { return; }

    this._context.fillStyle = fg;

    var chars = [].concat(ch);
    for (var i=0;i<chars.length;i++) {
        //Normal x placement
        var tmpX = (x + 0.5) * this._spacingX;
        //Check added to change to right-to-left
        if(this._options['rightToLeft'] == true) {
            //Reversed x placement
            tmpX = this._context.canvas.width - ((x+0.5) * this._spacingX);
        }
        var tmpY = (y+0.5) * this._spacingY;
        this._context.fillText(chars[i], tmpX, tmpY);
    }
}

While this is really just a clever hack it has two benefits. The first is that for someone dealing with a language that's right-to-left, they can do all their layout right-to-left. Rather than doing the calculations of width - string.length etc, they can lay it out the same as someone writing in a left-to-right language would, from the author's perspective, x legitimately increases as one moves to the left. The second benefit is that because this is so low-level in the code, everything else just works. You don't need to change any other draw functions, it's all there.

Just by making the above tweak it's easy to go from displaying Arabic characters completely wrong: ltr

To completely right: rtl

Note: In both examples the bottom line is using ROT.Display.drawText and the correct Arabic Uncode characters for connecting words. Just by flipping the x coordinate in Rot.Display.drawNoCache Arabic text displays correctly. The downside to this solution is that once in RTL mode, displaying text in a left-to--right language has the exact same issue as right-to-left does today.

This is definitely not a solved issue and I'm willing to try to do the work to integrate it as described above. However I'd like to discuss if this is the best solution, whether it's a correct solution, and if it's something you'll pull into ROT.

Impact

As for why I believe this is important, one of the top five most spoken languages in the world (Arabic) is written right-to-left, along with a slew of other smaller languages. Allowing for this support would enable much closer to native development (outside of Javascript using English syntax) for the Middle East as a whole, along with expat communities world-wide that desire to hold on to their heritage.

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/1528048-support-for-right-to-left-languages?utm_campaign=plugin&utm_content=tracker%2F297828&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F297828&utm_medium=issues&utm_source=github).
ondras commented 10 years ago

Interesting, interesting.

I am particularly impressed by the simplicity of this fix.

The issue I see here is that in the real world usage, people will want to draw map-related visual stuff (such as the map, beings, items, ...) using LTR, but textual information (text buffer, inventory, questlist, attributes, ...) using RTL. This is achievable by chaging the relevant ROT.Display option before drawing stuff (using the setOptions() call), but doing so would force the developer to mentally maintain two coordinate systems at once.

I do not plan on writing a RTL game and I have zero experience with RTL languages in general, so I find it a bit difficult correctly deciding about this one. The proposed fix is easy and straightforward enough to be merged (we will need to modify all the other ROT.Display.Backend classes as well) soon, but I see two further places to seek for more relevant information:

1) a native RTL speaker might provide some insight about mixing a textual information with a character-based map display;

2) some well-known battle tested libraries (ncurses? libtcod?) might have been there before and dealt with RTL in some standardized way.

thesnarky1 commented 10 years ago

I like simple.

I would put out there that someone used to reading right-to-left may actually want to do their coordinate system right-to-left. It is definitely true that if someone just wants to include flavour text this solution on its own will not suffice.

This may mean that a combination is the answer: have a simple RTL display using the above quick fix and also take the time to implement a drawReverseText function that will do a bit more computation to properly reverse the text and line it up correctly the wrong-way-round. The combination of these would mean that regardless of how you wanted to address a coordinate system or String, the author already has the tools.

I will see if I can drum up a native speaker to weight on on the matter and do research as to how (and if) this has been handled before.

I'm willing to code up a pull request for you to evaluate the usefulness of if you'd like.

Norgg commented 10 years ago

It'd probably be a more complex solution and would involve storing just as much state, but I feel like I should mention the existence of http://en.wikipedia.org/wiki/Right-to-left_mark which was defined for just this situation.

On 8 April 2014 21:56, thesnarky1 notifications@github.com wrote:

I like simple.

I would put out there that someone used to reading right-to-left may actually want to do their coordinate system right-to-left. It is definitely true that if someone just wants to include flavour text this solution on its own will not suffice.

This may mean that a combination is the answer: have a simple RTL display using the above quick fix and also take the time to implement a drawReverseText function that will do a bit more computation to properly reverse the text and line it up correctly the wrong-way-round. The combination of these would mean that regardless of how you wanted to address a coordinate system or String, the author already has the tools.

I will see if I can drum up a native speaker to weight on on the matter and do research as to how (and if) this has been handled before.

I'm willing to code up a pull request for you to evaluate the usefulness of if you'd like.

Reply to this email directly or view it on GitHubhttps://github.com/ondras/rot.js/issues/32#issuecomment-39900829 .