diff --git a/lib/ical/helpers.js b/lib/ical/helpers.js index a8a5c60d..071bf08d 100644 --- a/lib/ical/helpers.js +++ b/lib/ical/helpers.js @@ -287,11 +287,22 @@ ICAL.helpers = { */ foldline: function foldline(aLine) { var result = ""; - var line = aLine || ""; - + var line = aLine || "", pos = 0, line_length = 0; + //pos counts position in line for the UTF-16 presentation + //line_length counts the bytes for the UTF-8 presentation while (line.length) { - result += ICAL.newLineChar + " " + line.substr(0, ICAL.foldLength); - line = line.substr(ICAL.foldLength); + var cp = line.codePointAt(pos); + if (cp < 128) ++line_length; + else if (cp < 2048) line_length += 2;//needs 2 UTF-8 bytes + else if (cp < 65536) line_length += 3; + else line_length += 4; //cp is less than 1114112 + if (line_length < ICAL.foldLength + 1) + pos += cp > 65535 ? 2 : 1; + else { + result += ICAL.newLineChar + " " + line.substring(0, pos); + line = line.substring(pos); + pos = line_length = 0; + } } return result.substr(ICAL.newLineChar.length + 1); }, diff --git a/test/stringify_test.js b/test/stringify_test.js index 987a90b6..e92e4cc1 100644 --- a/test/stringify_test.js +++ b/test/stringify_test.js @@ -113,6 +113,12 @@ suite('ICAL.stringify', function() { assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, false), "DESCRIPTION:foo" + N + "bar"); assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, true), "DESCRIPTION:foobar"); + var utf16_muscle = '\uD83D\uDCAA'; //in UTF-8 this is F0 DF 92 AA. If space/new line is inserted between the surrogates, then the JS Engine substitutes each stand-alone surrogate with REPLACEMENT CHARACTER 0xEF 0xBF 0xBD + subject.setValue(utf16_muscle); + assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, false), "DESCRIPTION:" + N + utf16_muscle);//verify new line is after ':', as otherwise the whole line is longer than ICAL.foldLength + subject.setValue('aa' + utf16_muscle + utf16_muscle + 'a' + utf16_muscle + utf16_muscle); + assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, false), "DESCRIPTION:aa" + N + utf16_muscle + utf16_muscle + 'a' + utf16_muscle + N + utf16_muscle);//verify that the utf16_muscle is moved as whole to a new line as it is 4 UTF-8 bytes + ICAL.foldLength = oldLength; }); });