Search code examples
delphiclipboardfiremonkeycopy-paste

How to copy HTML Formatted text As HTML to ClipBoard in Firemonkey


Using Delphi FireMonkey, I need to copy HTML formatted string to the clipboard in Delphi so that other application pasting it will see it as HTML.

I already trying to copy simple text through a hidden Memo component and it work but is copied as simple text. I need HTML format copy. Having it as text, when pasting in any rich text program it shows all the HTML tags instead of formatted text.

My Current approach for copying text to clipboard is:

procedure TForm1.Button1Click(Sender: TObject);
var
  SourceText: string;
begin
  SourceText := 'This is a <b>bold</b> html line';
  //I can use TMemo to copy it to clipboard like:
  Memo1.lines.Text := SourceText;
  Memo1.SelectAll;
  memo1.CopyToClipboard;
end;

But the problem is that if I paste the text copied on clipboard in Microsoft word it will be pasted as:

  • "This is a <b>bold</b> html line". I want and it should be like:

  • This is a bold html line

Note: I have read other discussions about how it can be done in windows but I need a Firemonkey solution for a cross platform application. Any help is appreciated.


Solution

  • To copy data to the clipboard in a cross platform way with FireMonkey is done thru the interface IFMXExtendedClipboardService that is obtained by calling the platform service:

    var
        Svc      : IFMXExtendedClipboardService;
    begin
        if not TPlatformServices.Current.SupportsPlatformService(IFMXExtendedClipboardService, Svc) then 
            Exit;  // Not clipboard supported
        // Code using the interface
    

    This interface has methods to copy/get text/images to/from clipboard: SetText, GetText, SetImage, GetImage.

    For other kind of data, the user must register the data format and then write/read data to/from clipboard: RegisterCustomFormat, IsCustomFormatRegistered, UnregisterCustomFormat, HasCustomFormat, GetCustomFormat, SetCustomFormat.

    In your question, you say you want to copy HTML formatted data to the clipboard. This can be translated to register the HTML format, create a stream with your data and call SetCustomFormat passing the format and stream.

    Any format can be used and transmitted to/from the clipboard using the above mentioned interface methods. The code below takes the stream stream and copy his content to the clipboard using ClipFormat:

    if TPlatformServices.Current.SupportsPlatformService(
                    IFMXExtendedClipboardService, Svc) then begin
        if not Svc.IsCustomFormatRegistered(ClipFormat) then
            Svc.RegisterCustomFormat(ClipFormat);
        Svc.SetCustomFormat(ClipFormat, Stream);
    end;
    

    A format must be registered only once hence the call to IsCustomFormatRegistered to prevent calling RegisterCustomFormat more than once.

    The name of the format is a simple string and can be anything. Copying and pasting applications must agree on the format name and the data format (The way the data is written in the stream).

    Writing HTML formatted data may be difficult because of styling. Taking just a simple copy of an complete HTML document will probably not render it correctly because of styles.

    If you transfer data thru the clipboard between two of your applications, you can do whatever you want. But transferring data between your application and another (You mentioned Microsoft Word in your question) is much more difficult.

    In Microsoft products and all others on the Windows platform, the HTML format in the clipboard is described here.

    The example you give in your question once properly formatted looks like this:

    Version:0.9
    StartHTML:00000144
    EndHTML:00000218
    StartFragment:00000167
    EndFragment:00000205
    StartSelection:00000167
    EndSelection:00000205
    <!DOCTYPE><HTML><BODY><P>This is a <b>bold</b> html line</P></BODY></HTML>
    

    The actual string is as above, with a CRLF at the end of each line. The format name is "HTML format".

    You see the sentence This is a <b>bold</b> html line must be surrounded by HTML tags to form a valid complete HTML document, and be preceded by a header made of a number of keyword:value pairs. The values are offset if the string. The keywords are quite self explanatory. The string is UTF8 which can be reduced to ANSI if you use HTML entities to represent special characters.

    I wrote a function to build the entire string:

    function FormatHtmlForClipboard(const HtmlSrc : UTF8String) : UTF8String;
    const
        Header = 'Version:0.9'             + #13#10 +
                 'StartHTML:00000000'      + #13#10 +
                 'EndHTML:00000000'        + #13#10 +
                 'StartFragment:00000000'  + #13#10 +
                 'EndFragment:00000000'    + #13#10 +
                 'StartSelection:00000000' + #13#10 +
                 'EndSelection:00000000'   + #13#10;
    var
        BodyStart : Integer;
        BodyEnd   : Integer;
        HdrLen    : Integer;
    begin
        Result := Header;
        BodyStart := Pos('<BODY>', String(HtmlSrc));
        if BodyStart <= 0 then
            raise Exception.Create('<BODY> tag not found');
        Inc(BodyStart, 6);
        BodyEnd := Pos('</BODY>', String(HtmlSrc));
        if BodyEnd <= 0 then
            raise Exception.Create('</BODY> tag not found');
    
        HdrLen := Length(Header) - 1;
        WriteNumberIntoString(HdrLen,                   'StartHTML:',      Result);
        WriteNumberIntoString(HdrLen + Length(HtmlSrc), 'EndHTML:',        Result);
        WriteNumberIntoString(HdrLen + BodyStart,       'StartFragment:',  Result);
        WriteNumberIntoString(HdrLen + BodyEnd,         'EndFragment:',    Result);
        WriteNumberIntoString(HdrLen + BodyStart,       'StartSelection:', Result);
        WriteNumberIntoString(HdrLen + BodyEnd,         'EndSelection:',   Result);
        Result := Result + HtmlSrc;
    end;
    
    procedure WriteNumberIntoString(
        N        : Integer;
        const At : UTF8String;
        var S    : UTF8String);
    var
        I : Integer;
        V : UTF8String;
    begin
        I := Pos(At, S);
        if I <= 0 then
            Exit;
        I := I + Length(At);
        V := UTF8String(Format('%08.8d', [N]));
        Move(V[1], S[I], Length(V));
    end;
    

    With all that, the function to copy a HTML formatted data to the clipboard looks like:

    procedure CopyHtmlToClipboard(const HtmlSrc : UTF8String);
    var
        Svc      : IFMXExtendedClipboardService;
        Stream   : TStringStream;
        HtmlData : UTF8String;
    const
        ClipFormat = 'HTML format';   // This is what Windows expect
                                      // Maybe other platform want something else
    begin
        HtmlData := FormatHtmlForClipboard(HtmlSrc);
        Stream   := TStringStream.Create(HtmlData);
        if TPlatformServices.Current.SupportsPlatformService(
                        IFMXExtendedClipboardService, Svc) then begin
            if not Svc.IsCustomFormatRegistered(ClipFormat) then
                Svc.RegisterCustomFormat(ClipFormat);
            Svc.SetCustomFormat(ClipFormat, Stream);
        end;
    end;
    

    You shall use the function like this:

    procedure TForm1.Button1Click(Sender: TObject);
    begin
        CopyHtmlToClipboard(
            '<!DOCTYPE><HTML><BODY><P>' +
            'This is a <b>bold</b> html line' +    //<== Your actual HTML text
            '</P></BODY></HTML>');
    end;