devexpressc++builder-2010

How to load a TMemoryStream from a UnicodeString


I am trying to fill a DevExpress VCL TdxRichEditControl with the string of a calculated RTF file without taking the time to save the file. So, here is a demo project:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    UnicodeString txt = "{\\rtf1\\deff0{\\fonttbl{\\f0 Calibri;}{\\f1 Arial Unicode MS;}{\\f2 Britannic Bold;}}{\\colortbl ;\\red0\\green0\\blue255 ;\\red255\\green0\\blue0 ;}{\\*\\defchp \\fs22}{\\*\\defpap \\sl275\\slmult1\\sa200}{\\stylesheet {\\ql\\sl275\\slmult1\\sa200\\fs22 Normal;}{\\*\\cs1\\fs22 Default Paragraph Font;}{\\*\\cs2\\sbasedon1\\fs22 Line Number;}{\\*\\cs3\\ul\\fs22\\cf1 Hyperlink;}{\\*\\ts4\\tsrowd\\fs22\\ql\\sl275\\slmult1\\sa200\\tscellpaddfl3\\tscellpaddl108\\tscellpaddfb3\\tscellpaddfr3\\tscellpaddr108\\tscellpaddft3\\tsvertalt\\cltxlrtb Normal Table;}{\\*\\ts5\\tsrowd\\sbasedon4\\fs22\\ql\\sl275\\slmult1\\sa200\\trbrdrt\\brdrs\\brdrw10\\trbrdrl\\brdrs\\brdrw10\\trbrdrb\\brdrs\\brdrw10\\trbrdrr\\brdrs\\brdrw10\\trbrdrh\\brdrs\\brdrw10\\trbrdrv\\brdrs\\brdrw10\\tscellpaddfl3\\tscellpaddl108\\tscellpaddfr3\\tscellpaddr108\\tsvertalt\\cltxlrtb Table Simple 1;}}{\\*\\listoverridetable}\\nouicompat\\splytwnine\\htmautsp\\sectd\\pard\\plain\\ql\\sl275\\slmult1\\sa200{\\f1\\fs28\\cf1 This is in blue }{\\b\\i\\f2\\fs40\\cf2 Red bold}\\b\\i\\fs22\\cf2\\par}";
    TMemoryStream *AStream = new TMemoryStream();

    AStream->WriteBuffer(txt.c_str(), txt.Length());
    AStream->Position = 0;

    AStream->SaveToFile("C:/Trash/Test.rtf");
    AStream->Position = 0;

    dxRichEditControl1->Document->InsertDocumentContent(dxRichEditControl1->Document->Range->Start, AStream, TdxRichEditDocumentFormat::Rtf);
    AStream->Free();
}

I saved it to a file in this demo to see what is going wrong and found the file contains a space in every other character (so, of course, the component I was trying to fill with this data failed to display anything). When I look at the file, this is the type of stuff I see:

{ \ r t f 1 \ d e f f 0 { \ f o n t t b l { \ f 0   C a l i b r i ; } { \ f 1   A r i a l   U n i c o d e   M S ; } { \ f 2   B r i t a n n i c   B o l d ; } } { \ c o l

I want to stick with storing txt variable as a UnicodeString, because the real project has a class that calculates that string, but any ideas how to fix this issue? I realize I could probably do something time consuming like loop through and eliminate every other character, but I want some efficient solution.


Solution

  • RTF is a 7-bit ASCII format, so you should not be treating it as UTF-16 at all. The correct solution is to simply change the UnicodeString to an AnsiString:

    AnsiString txt = ...;
    

    The "extra spaces" are due to 0x00 bytes being added when the 7-bit ASCII characters are extended to 16-bit values in UTF-16.

    Besides, you are not even writing the UnicodeString to the TMemoryStream correctly anyway. Since WriteBuffer() deals with raw bytes, not string characters, you would need to multiply txt.Length() by sizeof(System::WideChar) to get the correct byte count:

    UnicodeString txt = ...;
    ...
    AStream->WriteBuffer(txt.c_str(), txt.Length() * sizeof(WideChar));
    

    If you change to AnsiString, you don't need to multiply since sizeof(AnsiChar) is 1.

    But, if you are set on sticking with UnicodeString, then at least use TStringStream instead, so that you can specify a byte encoding for it:

    UnicodeString txt = ...;
    TStringStream *AStream = new TStringStream(txt, TEncoding::ASCII);
    ...
    

    Also, since the stream is created with new, you should change AStream->Free(); to delete AStream; instead. NEVER call TObject::Free() directly in C++, that is a Delphi idiom. The C++ way to free something that is allocated with new is delete. It will call the TObject destructor as expected, just as TObject::Free() does.