Search code examples
c++stringandroid-ndk

C++: Text was not handled properly, first \r\n was not parsed


I'm porting a open source game client from PC to Android, however when dealing with NPC chat, some lines are not parsed correctly:

Original text from server:

formatted_text_ = "Now...ask me any questions you may have on traveling!!\r\n#L0##bHow do I move?#l\r\n#L1#How do I take down the monsters?#l\r\n#L2#How can I pick up an item?#l\r\n#L3#What happens when I die?#l\r\n#L4#When can I choose a job?#l\r\n#L5#Tell me more about this island!#l\r\n#L6#What should I do to become a Warrior?#l\r\n#L7#What should I do to become a Bowman?#l\r\n#L8#What should I do to become a Magician?#l\r\n#L9#What should I do to become a Thief?#l\r\n#L10#How do I raise the character stats? (S)#l\r\n#L11#How do I check the items that I just picked up?#l\r\n#L12#How do I put on an item?#l\r\n#L13#How do I check out the items that I'm wearing?#l\r\n#L14#What are skills? (K)#l\r\n#L15#How do I get to Victoria Island?#l\r\n#L16#What are mesos?#l#k"

Second line "How do I move" is not started as a new line, other lines ended with \r\n are parsed normally by following a new line.

How \r and \n are handled:

            switch (text[first]) {
                case '\\':
                    if (first + 1 < last) {
                        switch (text[first + 1]) {
                            case 'n':
                                linebreak = true;
                                break;
                            case 'r':
                                linebreak = ax_ > 0;
                                break;
                        }

                        skip++;
                    }

                    skip++;
                    break;

When line break is true or length exceeds max width, a new line should be added:

        bool newword = skip > 0;
        bool newline = linebreak || ax_ + wordwidth > max_width_;

        if (newword || newline) {
            add_word(prev, first, last_font, last_color);
        }

        if (newline) {
            add_line();

            endy_ = ay_;
            ax_ = 0;
            ay_ += font_.linespace();

            if (!lines_.empty()) {
                ay_ -= line_adj_;
            }
        }

enter image description here How formatted_text_ was handled:

https://github.com/speedyHKjournalist/OpenMapleClient/blob/521af535bb65fb231296d02f1d19a6f38e77673d/app/src/main/cpp/src/IO/UITypes/UINpcTalk.cpp#L141

https://github.com/speedyHKjournalist/OpenMapleClient/blob/73ddd15f443cc0ab9f796ce362a67d73e5e453ad/app/src/main/cpp/src/Graphics/GraphicsGL.cpp#L617

https://github.com/speedyHKjournalist/OpenMapleClient/blob/73ddd15f443cc0ab9f796ce362a67d73e5e453ad/app/src/main/cpp/src/Graphics/GraphicsGL.cpp#L657


Solution

  • Those \r\n sequences are two bytes. \r is carriage return. and \n is a line feed char. Windows prefers \r\n for end of line. Unix and everywhere else (including Android) prefers just \n for end of line.

    Easiest thing to do is simply treat \r, \n, and \r\n as valid end of line markers. So let's just normalize your formatted string such that \n is the defacto end of line marker:

        std::string s = formatted_text_;
        std::string t;
    
        for (size_t i = 0; i < s.size(); i++) {
            if (s[i] == '\r') {
                t += '\n';
                if ((i + 1 < s.size()) && (s[i + 1] == '\n')) {
                    i++;
                }
            }
            else {
                t += s[i];
            }
        }
    

    The outcome of that is that t is now a string of the form:

    "Now...ask me any questions you may have on traveling!!\n#L0##bHow do I move?#l\n#L1#How do I take down the monsters?#l\n#L2#How can I pick up an item?#l\n#L3#What happens when I die?#l\n#L4#When can I choose a job?#l\n..."
    

    Since we now have one delimiter to indicate end of line, we can use getline to parse into a list of lines.

        std::istringstream iss(t);
        std::string line;
        while (std::getline(iss, line, '\n')) {
            lines.push_back(line);
        }
    

    lines is an array (vector) of strings as follows:

    #L0##bHow do I move?#l
    #L1#How do I take down the monsters?#l
    #L2#How can I pick up an item?#l
    #L3#What happens when I die?#l
    #L4#When can I choose a job?#l
    #L5#Tell me more about this island!#l
    #L6#What should I do to become a Warrior?#l
    #L7#What should I do to become a Bowman?#l
    #L8#What should I do to become a Magician?#l
    #L9#What should I do to become a Thief?#l
    #L10#How do I raise the character stats? (S)#l
    #L11#How do I check the items that I just picked up?#l
    #L12#How do I put on an item?#l
    #L13#How do I check out the items that I'm wearing?#l
    #L14#What are skills? (K)#l
    #L15#How do I get to Victoria Island?#l
    #L16#What are mesos?#l#k
    

    e.g. line[2] is equal to #L2#How can I pick up an item?#l. Those format specifiers of #L2 and #l for your game - I'll leave that as an exercise for you.

    note: #include <string> #include <vector> and #include <sstream> for the above code to work.