HOME - - - - - Lazarus Tutorials TOC - - - - - - Other material for programmers

Working with Rich Text Format files ("RTF")

Changing the color, style, etc, of selected bits of text
(And a "bonus" alternative discussion of "filestreams"!)

filename: LT4Nh-rtf.htm

This page is "browser friendly". Make your browser window as wide as you want it. The text will flow nicely for you. It is easier to read in a narrow window. With most browsers, pressing plus, minus or zero while the control key (ctrl) is held down will change the texts size. (Enlarge, reduce, restore to default, respectively.) (This is more fully explained, and there's another tip, at my Power Browsing page.)

Written a bit hastily... but Good Stuff here, I think.

As this stands, you may wonder "What is the point?"

As it stands... you're right! There's not a lot of point to the application presented here! As anything you'd use for its own merits.

So why did I write it?

I was starting to build an application for a quite specific want. It will scan two similar text files, and allow the user to choose which version, where differences exist, to put into a final merged version which will have elements of both of the "input" files.

As I dreamed of what would be, I "saw" in my mind's eye, a form with two memos on it. One for each of the input files.

There would be just one scroll bar... it would scroll the text in BOTH of the windows.

Where the text is identical, the text will be black. Where a file has something the other file doesn't have, that text will be in green. Where there's a block of text that appears, sort of, to be present... but in different forms... in both files, it will be in red, in both memos.

Now.. with an ordinary TMemo memo, you can't have different bits in different colors. Nor can you have bold, italic, etc text.

Enter TRichText

Happily, however, there is a free "official" add-on package which gives you something quite like a TMemo memo... the TRichMemo memo.

This essay takes you thorough adding the package, and making some use of it.

The magic the colors, etc, comes to you via the wonders of the .rtf file format. It has been around for a long time. Many applications can open files of this format and LibreOffice Writer among them. A simple text editor can also open one... though you'll see "gibberish". (That "gibberish" works the way the code behind a page of .html works. It is a markup language. Opening either an .html document or a .rtf document lets you see "what's happening".

Briefly back to my goal...

((q-alt text for image))

As I said, this tutorial was born out of research I was doing along the road to writing an application that I want for my own purposes.

That application will ask the user to specify two files. They will contain almost the same material.

The application will then display them in a manner something like...





Getting started with RichMemo

Start at...

https://wiki.lazarus.freepascal.org/RichMemo

I'm assuming you already have a Lazarus IDE installed on your computer.

Ever notice how the devil lurks in the details? I did the following 28 Nov 20 (staying at home to help defeat Covid) using Win 10, Lazarus 2.0.0 r60307, and though it will mean nothing to you, it may help me if I add "on pcVa2". (That tells me which of my computers I did this on.)

That page says...

"The easiest way to install the stable version of the component is via the Online-Package-Manager included in current Lazarus versions."

... and it says a bunch of other stuff about how you might install the package. I didn't need to read any further. (The "easiest way" worked for me". I just.....)

I launched my Lazarus.

Did a "File/ Close All" so that nothing remained open in the IDE.

Invoked "Package/ Online Package Manager".

That brought up...

((q-alt text for image))

I typed "Rich" into the edit box to the right of "Filer by", so the manager would look for packages with names containing "Rich". (I left the type of package set for "Packages".)

That brought up a choice of "LazRichView" and "RichMemo". I went for "RichMemo": Ticked the box, invoked "Install"... "From Repository".

That launched a scary (to me!) rapidly passing sequence of "Opening Package"/"Compiling Package" messages, with "Success" appearing fairly quickly after the Opening/Compiling.


It was All Over in under a minute.

Then I was asked...

((q-alt text for image))

I gulped and said "Yes".

"Stuff" started happening. It looked a bit like when you launch a compile/run operation when using the IDE. LOTS of warnings in the "Messages" dialog, but eventually It All Went Away, and Lazarus restarted. (I was typing this at the time. Focus may have switched to Lazarus without me noticing. I may have unwittingly said "Proceed", and thus lost the chance to examine the messages when the "Rebuild... Normal" had finished.)

I was left looking at what seemed to be my usual Lazarus IDE. Ran an old project. All seemed to be well. (Then, and subsequently. Don't worry about the "seemed"!)

RTF

Time to talk about "RTF".

As I started to say earlier, "RTF" is an old file format. The letters stand for Rich Text Format.

It works a bit like HTML. There is plain text "behind" what you see on the screen if you load a file of RTF material into an editor that knows how RTF should be displayed.

Hello RTF World

Use an ordinary simple text editor... Notepad if you must, Textpad if you'll take my advice... to create a little file called "TestData.txt". Yes... just a simple .txt file... for now! Save it in the directory you are using for your Lazarus program.

Put exactly the following in it...

Hello RTF world.
Test file for the TRichMenu component.

(I said "exactly".. But as long as "RTF" appears somewhere, and the file is at least 32 characters long, it will "work"... but the button to change the words "file for" so they are red on the screen might make different characters red.)

Start a new application in your recently extended Lazarus. I called mine "LTN163-rtf".

Put a button on it. Call it buLoadTxtFile.

The component palette's "Common Controls" should be offering a new component now...

((q-alt text for image))

The tooltip should say "TRichMemo".

Make the button of your little Lazarus test application do...

procedure TLTN163-rtfF1.buLoadTxtFileClick(Sender: TObject);
begin
  RichMemo1.Lines.LoadFromFile('TestData.txt');
end;

Run that, click the button. The text should load into the RichMemo, and be visible to the user... but everything will be black for the time being.

Add another button. Call it buFindForThe.

[=== Digression begins: Right! Up to there, things raced along almost as fast as you've just read them. The next bit was "supposed" to be simple. Ha! I'd already "researched" the issues, and it was "easy". Three hours later I has something working. Aren't computers "fun".
--- Digression Ends===]

"All" I wanted was to turn some of the text a different color. Supposedly, to turn 5 characters, from the 3rd character in the memo, blue, all I needed was...

procedure TLDN163rtfF1.DoesNotWorkClick(Sender: TObject);
var fontTmp:TFont;
begin
  fontTmp:=RichMemo1.font;//To fill all of the properties with "starting values".
  fontTmp.Color:=clBlue;//Change the font color
  RichMemo1.SetTextAttributes(3,5,fontTmp);
end; 

It "works"... to change ALL of the text red! According to the web, the 3,5 should have made it happen to just a range of characters. Sigh. (See https://wiki.freepascal.org/RichMemo#SetTextAttributes

A little "gotcha"... there may well be subtle differences between how the application works on machines with different operating systems. Sigh. That it even tries to be multi-platform is, to me, very impressive. I'll happily settle for a slightly imperfect success... so far!... for the chance that I can move to Linux from wretched Windows someday, and carry on programming in Lazarus without major re-leaning.

This works...

Eventually I gave up trying to figure out why the 3 and the 5 were being ignored.

The following works. In it, I've made one bit of the text red, another green. I used a simple method and a fancy method to determine what part of the text will change color.

The same basic word... SetRangeColor... was used for both. Then only difference is in the setting of the two numbers which, between them, determine what part of the text will be colored.

In the simple method it is just that 8 characters from the 22nd in the text will be made red.

In the second, if "RTF" appears in the text, the first instance of those letters will be made green.

You can, by the way, use similar techniques to make bits of text "bold", "underlined", etc... or not. Or so I am told. Ha!

The following DOES WORK! At least today, under Win10.

procedure TLDN163rtfF1.buChangeColorsClick(Sender: TObject);
var boTmp:boolean;//Not strictly necessary... used to make code more readable
   iTextStart,iTextLength:integer;
   soSearchOptions:TSearchOptions;
begin
(*SPENT AGES TRYING TO GET "RichMemo1.SetTextAttributes" to work. No joy.
  See overloaded "SetTextAttributes" other form for more Good Stuff
  https://wiki.freepascal.org/RichMemo#SetRangeColor  *)

//Following starts with the 22nd character in the Rich(Test)Memo,
    //   and colors 8 characters blue.
  RichMemo1.SetRangeColor(22,8,clRed);

//Fancier... but only in how "start", "length" determined.
  //See https://wiki.freepascal.org/RichMemo#Search
  //SearchOptions:
  //https://sourceforge.net/p/lazarus-ccr/svn/3771/tree//components/richmemo/richmemo.pas
  //   TSearchOption = (soMatchCase, soWholeWord, soBackward);
  //   TSearchOptions = set of TSearchOption;

soSearchOptions:=[];//Prepare a value to pass in the 4th parameter.
boTmp:=RichMemo1.Search('RTF',0,999,soSearchOptions,iTextStart,iTextLength);
if boTmp then begin
   RichMemo1.SetRangeColor(iTextStart,iTextLength,clGreen);
   laMsg.caption:='';//laMsg not critical to changing the color.
      //See what "else" clause to learn what laMsg is for.
   end//no ; here
 else begin
   laMsg.caption:='String "RTF" not found';
   end;//else

end;//proc buChangeColorsClick

So! That's how to color bits of the text in a TRichMemo component.

Running into the surf

Ever feel like you are running into the surf? Something starts quite well, but just gets harder and harder, leaving you closer and closer to Just Giving Up.

So it was with this project. Started remarkably smoothly. (I can't remember the last time (before 11/10) that I added an extra library to my IDE. (I shy away from doing it, for reasons that seem good to me.) And that went swimmingly.

The "simple" matter of coloring text was a bit recalcitrant.

And then I reached the final challenge needed to craft a complete result: A way to save the tweaked text from the TRichEdit memo back to a disk file.

I'd failed to plan, in detail, where I was going.

Did I want to save the text as plain text, thus losing the colors, the italics, etc, I'd applied to help me work on the text? Or did I want to save it in the RTF format which I'd been using while the text was on the screen for editing.

I hadn't really thought about that.

When I started thinking about it, I realized that sometimes I'd want the one, other times, the other. (Being human, I wanted, as usual, to have it both ways!)

As it turns out, both are reasonably easy to do.

You have to understand working with filestreams... not a forte of mine, despite my earlier tutorial about filestreams. (Which is, at 11/20, nothing more than a working demo application, with sourcecode and some inline documentation.)

The idea of "filestreams", as I understand it, is that they are "abstracted" files. When you speak of a file on your hard drive, or a file loaded into a memo, or in the computers RAM, there are differences. If you take a step back, and take about what's in one of those files as a filestream, there are almost no differences arising from where the file is.

This is GOOD! Now, everything you learn about a filesteam will be useful to you when thinking about "files" in or on (at least) three places/ surfaces.

And the language has ways to take a file on a disk, make it a filestream, move it to, say, a memo.

Save as RTF

The following moves the text in our TRichMemo, store in on the hard drive, in the RTF format....

procedure TLDN163rtfF1.buSaveToFile_WITH_RTFClick(Sender: TObject);
//From http://lazplanet.blogspot.com/2013/12/create-rich-text-editor-for-yourself.html
//HA! WORKS!
//AND MUCH BETTER THAN MINE, anyway, even apart from fact mine didn't save the RTF info.

var
  fs : TFileStream;
begin
  //if SaveDialog1.Execute then begin
    fs := nil;
    try
//      fs := TFileStream.Create( Utf8ToAnsi(SaveDialog1.FileName), fmCreate);
    fs := TFileStream.Create( Utf8ToAnsi('TmpDemo.rtf'), fmCreate);

    RichMemo1.SaveRichText(fs);
      //Saved:=True;
      //Filename:=ExtractFileName(SaveDialog1.FileName);
      //Filepath:=ExtractFilePath(SaveDialog1.FileName);
      //Caption:=Filename;
    except
    end;
    fs.Free;
 // end;   //if SaveDialog1.Execute
end;

Save as simple text

The following moves the text in our TRichMemo, store in on the hard drive, in the RTF format. As presented, it always saves the text to "TmpDemo.rtf", in the same directory as the .exe is in. (And it will... without asking or warning... over-write any file of that name already in the folder.) If you put the right selection of remmed out lines back and rem out at least one of the current lines, you will have a more generally useful procedure. I adapted it as I have to "strip it down" to basics.

procedure TLDN163rtfF1.buSaveToFile_NO_RTFClick(Sender: TObject);
//I'd hoped I could just do...
//    RichMemo1.SaveRichText('TmpDemo.rtf');
//... but of course that would be too easy.

//See also:https://sheepdogguides.com/lut/lt2Ne-filestreams.htm

//Adapted from example at http://wiki.freepascal.org/TFileStream
//N.B.... This will not work without some improvement if another
//  application has the output file open/ locked when this application
//  tries to write to it.

(*THE FOLLOWING DOES ** NOT ** SAVE the "rtf markup". It only
saves the text. None of the colors you added to it while it was
in the TRichMemo memo will still be there!

As it happens... that's all I needed!!

I suspect others will want the "hidden stuff" that would cause the
file, on being reloaded (into an RTF-capable editor, like Libre Office),
to still have the colorings set previously.

This isn't "clever": It always saves the text to "TmpDemp.txt". "Fixing"
that is a separate issue, left out of this so that you can see
the heart of what is going on.
*)

var strm: TFileStream;
    bTmpL:byte;
    liLineInMemo,liPosnInLine:longint;
    sTmpL:string;
begin //buSaveToRTFfileClick
try //1
      strm := TFileStream.Create(Utf8ToAnsi('TmpDemo.txt'), fmCreate );//N.B.This will overwrite any existing file
      strm.position:=0;
      liLineInMemo:=0;//Line "0" is first line in MEMO. (Note indexing
      //difference between lines in memos and characters in string)
        try //2
        repeat
          sTmpL:=RichMemo1.lines[liLineInMemo];
          for liPosnInLine:=1 to length(sTmpL) do begin //For...
              bTmpL:=ord(sTmpL[liPosnInLine]);//sTmpL[1] is 1st char in string.
              strm.WriteByte(bTmpL);
              end;//For...
          inc(liLineInMemo);
          strm.WriteByte(13);//Insert CR in output
          strm.WriteByte(10);//Insert LF in output
        until liLineInMemo=RichMemo1.Lines.count;
      except//2
        on e:Exception do showmessage('problem... 17a21a');
      end;//e
    except //1
      on e:Exception do showmessage('problem... 17a21b');
    end;//try "1"
      FreeAndNil(strm);
end;//buSaveToFile_NO_RTFClick

How are "RTF" and "plain text" different?

If you put load copies of a simple text which has been saved from the application we have been discussing into Libre Office Writer, they look pretty similar, except that the some of the text in the .rtf is in color. (Fancier use of the RTF would also allow fonts, different letter sizes, etc.)

If you look at them with a simple text editor, you get the following from the .txt file...

To show how RTF and simple text look "under
the skin.

And this from the .rtf file. (I have changed where new lines start, and added some indents.)

{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang2057
    {\fonttbl{\f0\fnil\fcharset0 Segoe UI;}{\f1\fnil Segoe UI;
    }
}
{\colortbl ;\red0\green128\blue0;\red255\green0\blue0;}
{\*\generator Riched20 10.0.19041}\viewkind4\uc1 \pard\f0\fs18
To show how
    \cf1 RTF\cf0  and si\cf2 mple tex\cf0 t
    look "under the skin.\f1\par\par
}

Each has its uses.



Sourcecode provided

You can download a .zip file (LDN163rtf.zip) with the example of using RTF (and filestreams!). It has all the sourcecode, and a copy of the compiled .exe, as well, so you can have a play with that even before you bother to fire up your Lazarus.





Search across all my sites with the Google search...

Custom Search


To search THIS site.... (Go to my other sites, below, and use their search buttons if you want to search them.)

index sitemap advanced
search engine by freefind

Site Map    What's New    Search

The search engine merely looks for the words you type, so....
*    Spell them properly.
*    Don't bother with "How do I get rich?" That will merely return pages with "how", "do", "I"....

Please also note that I have two other sites, and that this search will not include them. They have their own search buttons.

My SheepdogSoftware.co.uk site.

My site at Arunet.


Ad from page's editor: Yes.. I do enjoy compiling these things for you. I hope they are helpful. However... this doesn't pay my bills!!! Sheepdog Software is supposed to help do that, so if you found this stuff useful, (and you run a Windows or MS-DOS PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.

Link to Lazarus Tutorials main page

To email this page's editor, Tom Boyd.... Editor's email address. Suggestions welcomed! Please cite "LT4Nh-rtf.htm".




Valid HTML 4.01 Transitional Page has been tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes.

AND passes... Valid CSS!

If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....