unit rtfutils; interface uses richedit, windows; function IsRTF(const Value: PWideChar): Boolean; //used for Export only function GetRichRTFW(RichEditHandle: THANDLE; var RTFStream: PWideChar; SelectionOnly, PlainText, NoObjects, PlainRTF: Boolean): Integer; function GetRichRTFA(RichEditHandle: THANDLE; var RTFStream: PAnsiChar; SelectionOnly, PlainText, NoObjects, PlainRTF: Boolean): Integer; function GetRichString(RichEditHandle: THANDLE; SelectionOnly: Boolean = false): PWideChar; function SetRichRTFW(RichEditHandle: THANDLE; const RTFStream: PWideChar; SelectionOnly, PlainText, PlainRTF: Boolean): Integer; function SetRichRTFA(RichEditHandle: THANDLE; const RTFStream: PAnsiChar; SelectionOnly, PlainText, PlainRTF: Boolean): Integer; function FormatString2RTFW(Source: PWideChar; Suffix: PAnsiChar = nil): PAnsiChar; function FormatString2RTFA(Source: PAnsiChar; Suffix: PAnsiChar = nil): PAnsiChar; procedure ReplaceCharFormatRange(RichEditHandle: THANDLE; const fromCF, toCF: CHARFORMAT2; idx, len: Integer); procedure ReplaceCharFormat(RichEditHandle: THANDLE; const fromCF, toCF: CHARFORMAT2); function GetTextLength(RichEditHandle:THANDLE): Integer; function GetTextRange (RichEditHandle:THANDLE; cpMin,cpMax: Integer): PWideChar; function BitmapToRTF(pict: HBITMAP): PAnsiChar; implementation uses common; function IsRTF(const Value: PWideChar): Boolean; const RTF_BEGIN_1 = '{\RTF'; RTF_BEGIN_2 = '{URTF'; begin Result := (StrPosW(Value,RTF_BEGIN_1) = Value) or (StrPosW(Value,RTF_BEGIN_2) = Value); end; type PTextStream = ^TTextStream; TTextStream = record Size: Integer; case Boolean of false: (Data: PAnsiChar); true: (DataW: PWideChar); end; function RichEditStreamLoad(dwCookie: DWORD_PTR; pbBuff: PByte; cb: Longint; var pcb: Longint): dword; stdcall; var pBuff: PAnsiChar; begin with PTextStream(dwCookie)^ do begin pBuff := Data; pcb := Size; if pcb > cb then pcb := cb; Move(pBuff^, pbBuff^, pcb); Inc(Data, pcb); Dec(Size, pcb); end; Result := 0; end; function RichEditStreamSave(dwCookie: DWORD_PTR; pbBuff: PByte; cb: Longint; var pcb: Longint): dword; stdcall; var prevSize: Integer; begin with PTextStream(dwCookie)^ do begin prevSize := Size; Inc(Size,cb); ReallocMem(Data,Size); Move(pbBuff^,(Data+prevSize)^,cb); pcb := cb; end; Result := 0; end; function _GetRichRTF(RichEditHandle: THANDLE; TextStream: PTextStream; SelectionOnly, PlainText, NoObjects, PlainRTF, Unicode: Boolean): Integer; var es: TEditStream; Format: Longint; begin format := 0; if SelectionOnly then Format := Format or SFF_SELECTION; if PlainText then begin if NoObjects then Format := Format or SF_TEXT else Format := Format or SF_TEXTIZED; if Unicode then Format := Format or SF_UNICODE; end else begin if NoObjects then Format := Format or SF_RTFNOOBJS else Format := Format or SF_RTF; if PlainRTF then Format := Format or SFF_PLAINRTF; // if Unicode then format := format or SF_USECODEPAGE or (CP_UTF16 shl 16); end; TextStream^.Size := 0; TextStream^.Data := nil; es.dwCookie := DWORD_PTR(TextStream); es.dwError := 0; es.pfnCallback := @RichEditStreamSave; SendMessage(RichEditHandle, EM_STREAMOUT, format, LPARAM(@es)); Result := es.dwError; end; function GetRichRTFW(RichEditHandle: THANDLE; var RTFStream: PWideChar; SelectionOnly, PlainText, NoObjects, PlainRTF: Boolean): Integer; var Stream: TTextStream; begin Result := _GetRichRTF(RichEditHandle, @Stream, SelectionOnly, PlainText, NoObjects, PlainRTF, PlainText); if Assigned(Stream.DataW) then begin if PlainText then StrDupW(RTFStream, Stream.DataW, Stream.Size div SizeOf(WideChar)) else AnsiToWide(Stream.Data, RTFStream, CP_ACP); FreeMem(Stream.Data, Stream.Size); end else RTFStream := nil; end; function GetRichRTFA(RichEditHandle: THANDLE; var RTFStream: PAnsiChar; SelectionOnly, PlainText, NoObjects, PlainRTF: Boolean): Integer; var Stream: TTextStream; begin Result := _GetRichRTF(RichEditHandle, @Stream, SelectionOnly, PlainText, NoObjects, PlainRTF, False); if Assigned(Stream.Data) then begin StrDup(RTFStream, Stream.Data, Stream.Size - 1); FreeMem(Stream.Data, Stream.Size); end else RTFStream := nil; end; function GetRichString(RichEditHandle: THANDLE; SelectionOnly: Boolean = false): PWideChar; begin GetRichRTFW(RichEditHandle,Result,SelectionOnly,True,True,False); end; function _SetRichRTF(RichEditHandle: THANDLE; TextStream: PTextStream; SelectionOnly, PlainText, PlainRTF, Unicode: Boolean): Integer; var es: TEditStream; Format: Longint; begin Format := 0; if SelectionOnly then Format := Format or SFF_SELECTION; if PlainText then begin Format := Format or SF_TEXT; if Unicode then Format := Format or SF_UNICODE; end else begin Format := Format or SF_RTF; if PlainRTF then Format := Format or SFF_PLAINRTF; // if Unicode then format := format or SF_USECODEPAGE or (CP_UTF16 shl 16); end; es.dwCookie := LPARAM(TextStream); es.dwError := 0; es.pfnCallback := @RichEditStreamLoad; SendMessage(RichEditHandle, EM_STREAMIN, format, LPARAM(@es)); Result := es.dwError; end; function SetRichRTFW(RichEditHandle: THANDLE; const RTFStream: PWideChar; SelectionOnly, PlainText, PlainRTF: Boolean): Integer; var Stream: TTextStream; Buffer: PAnsiChar; begin if PlainText then begin Stream.DataW := RTFStream; Stream.Size := StrLenW(RTFStream) * SizeOf(WideChar); Buffer := nil; end else begin WideToAnsi(RTFStream, Buffer, CP_ACP); Stream.Data := Buffer; Stream.Size := StrLen(Buffer); end; Result := _SetRichRTF(RichEditHandle, @Stream, SelectionOnly, PlainText, PlainRTF, PlainText); mFreeMem(Buffer); end; function SetRichRTFA(RichEditHandle: THANDLE; const RTFStream: PAnsiChar; SelectionOnly, PlainText, PlainRTF: Boolean): Integer; var Stream: TTextStream; begin Stream.Data := RTFStream; Stream.Size := StrLen(RTFStream); Result := _SetRichRTF(RichEditHandle, @Stream, SelectionOnly, PlainText, PlainRTF, False); end; function FormatString2RTFW(Source: PWideChar; Suffix: PAnsiChar = nil): PAnsiChar; var Text: PWideChar; res: PAnsiChar; buf: array [0..15] of AnsiChar; len: integer; begin // calculate len len:=Length('{\uc1 '); Text := PWideChar(Source); while Text[0] <> #0 do begin if (Text[0] = #13) and (Text[1] = #10) then begin inc(len,Length('\par ')); Inc(Text); end else case Text[0] of #10: inc(len,Length('\par ')); #09: inc(len,Length('\tab ')); '\', '{', '}': inc(len,2); else if Word(Text[0]) < 128 then inc(len) else inc(len,3+IntStrLen(Word(Text[0]),10)); end; Inc(Text); end; inc(len,StrLen(Suffix)+2); // replace Text := PWideChar(Source); GetMem(Result,len); res:=StrCopyE(Result,'{\uc1 '); while Text[0] <> #0 do begin if (Text[0] = #13) and (Text[1] = #10) then begin res:=StrCopyE(res,'\par '); Inc(Text); end else case Text[0] of #10: res:=StrCopyE(res,'\par '); #09: res:=StrCopyE(res,'\tab '); '\', '{', '}': begin res^:='\'; inc(res); res^:=AnsiChar(Text[0]); inc(res); end; else if Word(Text[0]) < 128 then begin res^:=AnsiChar(Word(Text[0])); inc(res); end else begin res:=StrCopyE( StrCopyE(res,'\u'), IntToStr(buf,Word(Text[0]))); res^:='?'; inc(res); end; end; Inc(Text); end; res:=StrCopyE(res, Suffix); res^:='}'; inc(res); res^:=#0; end; function FormatString2RTFA(Source: PAnsiChar; Suffix: PAnsiChar = nil): PAnsiChar; var Text,res: PAnsiChar; len: integer; begin // calculate len len:=1; Text := PAnsiChar(Source); while Text[0] <> #0 do begin if (Text[0] = #13) and (Text[1] = #10) then begin inc(len,Length('\line ')); Inc(Text); end else case Text[0] of #10: inc(len,Length('\line ')); #09: inc(len,Length('\tab ')); '\', '{', '}': inc(len,2); else inc(len); end; Inc(Text); end; inc(len,StrLen(Suffix)+2); // replace Text := PAnsiChar(Source); GetMem(Result,len); res:=Result; res^ := '{'; inc(res); while Text[0] <> #0 do begin if (Text[0] = #13) and (Text[1] = #10) then begin res:=StrCopyE(res,'\line '); Inc(Text); end else case Text[0] of #10: res:=StrCopyE(res,'\line '); #09: res:=StrCopyE(res,'\tab '); '\', '{', '}': begin res^:='\'; inc(res); res^:=Text[0]; inc(res); end; else res^:=Text[0]; inc(res); end; Inc(Text); end; res:=StrCopyE(res, Suffix); res^:='}'; inc(res); res^:=#0; end; function GetTextLength(RichEditHandle: THANDLE): Integer; var gtxl: GETTEXTLENGTHEX; begin gtxl.flags := GTL_DEFAULT or GTL_PRECISE; gtxl.codepage := 1200; // Unicode gtxl.flags := gtxl.flags or GTL_NUMCHARS; Result := SendMessage(RichEditHandle, EM_GETTEXTLENGTHEX, WPARAM(@gtxl), 0); end; procedure ReplaceCharFormatRange(RichEditHandle: THANDLE; const fromCF, toCF: CHARFORMAT2; idx, len: Integer); var cr: CHARRANGE; cf: CHARFORMAT2; loglen: Integer; res: DWord; begin if len = 0 then exit; cr.cpMin := idx; cr.cpMax := idx + len; SendMessage(RichEditHandle, EM_EXSETSEL, 0, LPARAM(@cr)); ZeroMemory(@cf, SizeOf(cf)); cf.cbSize := SizeOf(cf); cf.dwMask := fromCF.dwMask; res := SendMessage(RichEditHandle, EM_GETCHARFORMAT, SCF_SELECTION, LPARAM(@cf)); if (res and fromCF.dwMask) = 0 then begin if len = 2 then begin // wtf, msdn tells that cf will get the format of the first AnsiChar, // and then we have to select it, if format match or second, if not // instead we got format of the last AnsiChar... weired if (cf.dwEffects and fromCF.dwEffects) = fromCF.dwEffects then Inc(cr.cpMin) else Dec(cr.cpMax); SendMessage(RichEditHandle, EM_EXSETSEL, 0, LPARAM(@cr)); SendMessage(RichEditHandle, EM_SETCHARFORMAT, SCF_SELECTION, LPARAM(@toCF)); end else begin loglen := len div 2; ReplaceCharFormatRange(RichEditHandle, fromCF, toCF, idx, loglen); ReplaceCharFormatRange(RichEditHandle, fromCF, toCF, idx + loglen, len - loglen); end; end else if (cf.dwEffects and fromCF.dwEffects) = fromCF.dwEffects then SendMessage(RichEditHandle, EM_SETCHARFORMAT, SCF_SELECTION, LPARAM(@toCF)); end; procedure ReplaceCharFormat(RichEditHandle: THANDLE; const fromCF, toCF: CHARFORMAT2); begin ReplaceCharFormatRange(RichEditHandle,fromCF,toCF,0,GetTextLength(RichEditHandle)); end; function GetTextRange(RichEditHandle: THANDLE; cpMin,cpMax: Integer): PWideChar; var tr: TextRangeW; begin tr.chrg.cpMin := cpMin; tr.chrg.cpMax := cpMax; GetMem(Result,(cpMax-cpMin+1)*SizeOf(WideChar)); tr.lpstrText := Result; SendMessageW(RichEditHandle,EM_GETTEXTRANGE,0,LPARAM(@tr)); end; { Direct Bitmap to RTF insertion } function BytesPerScanline(PixelsPerScanline, BitsPerPixel, Alignment: Longint): Longint; begin Dec(Alignment); Result := ((PixelsPerScanline * BitsPerPixel) + Alignment) and not Alignment; Result := Result div 8; end; procedure InitializeBitmapInfoHeader(Bitmap: HBITMAP; var BI: TBitmapInfoHeader; Colors: Integer); var DS: TDIBSection; Bytes: Integer; begin DS.dsbmih.biSize := 0; Bytes := GetObject(Bitmap, SizeOf(DS), @DS); if Bytes = 0 then {InvalidBitmap} else if (Bytes >= (sizeof(DS.dsbm) + sizeof(DS.dsbmih))) and (DS.dsbmih.biSize >= DWORD(sizeof(DS.dsbmih))) then BI := DS.dsbmih else begin FillChar(BI, sizeof(BI), 0); with BI, DS.dsbm do begin biSize := SizeOf(BI); biWidth := bmWidth; biHeight := bmHeight; end; end; case Colors of 2: BI.biBitCount := 1; 3..16: begin BI.biBitCount := 4; BI.biClrUsed := Colors; end; 17..256: begin BI.biBitCount := 8; BI.biClrUsed := Colors; end; else BI.biBitCount := DS.dsbm.bmBitsPixel * DS.dsbm.bmPlanes; end; BI.biPlanes := 1; if BI.biClrImportant > BI.biClrUsed then BI.biClrImportant := BI.biClrUsed; if BI.biSizeImage = 0 then BI.biSizeImage := BytesPerScanLine(BI.biWidth, BI.biBitCount, 32) * Abs(BI.biHeight); end; procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: DWORD; var ImageSize: DWORD; Colors: Integer); var BI: TBitmapInfoHeader; begin InitializeBitmapInfoHeader(Bitmap, BI, Colors); if BI.biBitCount > 8 then begin InfoHeaderSize := SizeOf(TBitmapInfoHeader); if (BI.biCompression and BI_BITFIELDS) <> 0 then Inc(InfoHeaderSize, 12); end else if BI.biClrUsed = 0 then InfoHeaderSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) * (1 shl BI.biBitCount) else InfoHeaderSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) * BI.biClrUsed; ImageSize := BI.biSizeImage; end; procedure GetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: DWORD; var ImageSize: DWORD); begin InternalGetDIBSizes(Bitmap, InfoHeaderSize, ImageSize, 0); end; function InternalGetDIB(Bitmap: HBITMAP; Palette: HPALETTE; var BitmapInfo; var Bits; Colors: Integer): Boolean; var OldPal: HPALETTE; DC: HDC; begin InitializeBitmapInfoHeader(Bitmap, TBitmapInfoHeader(BitmapInfo), Colors); OldPal := 0; DC := CreateCompatibleDC(0); try if Palette <> 0 then begin OldPal := SelectPalette(DC, Palette, False); RealizePalette(DC); end; Result := GetDIBits(DC, Bitmap, 0, TBitmapInfoHeader(BitmapInfo).biHeight, @Bits, TBitmapInfo(BitmapInfo), DIB_RGB_COLORS) <> 0; finally if OldPal <> 0 then SelectPalette(DC, OldPal, False); DeleteDC(DC); end; end; function GetDIB(Bitmap: HBITMAP; Palette: HPALETTE; var BitmapInfo; var Bits): Boolean; begin Result := InternalGetDIB(Bitmap, Palette, BitmapInfo, Bits, 0); end; const HexDigitChr: array [0..15] of AnsiChar = ('0','1','2','3','4','5','6','7', '8','9','A','B','C','D','E','F'); function BitmapToRTF(pict: HBITMAP): PAnsiChar; const prefix = '{\rtf1 {\pict\dibitmap '; postfix = ' }}'; var tmp, bi, bb, rtf: PAnsiChar; bis, bbs: cardinal; len,cnt: integer; begin GetDIBSizes(pict, bis, bbs); GetMem(bi, bis); GetMem(bb, bbs); GetDIB(pict, {pict.Palette}0, bi^, bb^); len:=(bis+bbs)*2+cardinal(Length(prefix)+Length(postfix))+1; GetMem(result,len); rtf:=StrCopyE(result,prefix); tmp:=bi; for cnt := 0 to bis-1 do begin rtf^ := HexDigitChr[ord(tmp^) shr 4]; inc(rtf); rtf^ := HexDigitChr[ord(tmp^) and $F]; inc(rtf); inc(tmp); end; tmp:=bb; for cnt := 0 to bbs-1 do begin rtf^ := HexDigitChr[ord(tmp^) shr 4]; inc(rtf); rtf^ := HexDigitChr[ord(tmp^) and $F]; inc(rtf); inc(tmp); end; StrCopy(rtf,postfix); FreeMem(bi); FreeMem(bb); end; initialization finalization end.