Wednesday, November 19, 2008

New TPen properties

1. What is a Pen?

You know we are using pens when we draw a line or a shape border on a graphic canvas. A Pen defines how our lines will look. It can be thick or thin, dotted or solid, color or black. So lets review what properties a pen can have?

First of all pens can be cosmetic and geometric. Cosmetic pens are always 1 pixel wide, while geometric pens can have any width. But width of geometric pens are defined in world units and thus depends on transforms (scaling, rotation, shear, reflection) applied to canvas. Operations with cosmetic pens are faster.

The next pen property is pattern. Different libraries suggest different sets of default patterns (dot, dash, dash dot, dash dot dot, etc). Some of them don't contain any predefined pattern but allow to set a custom pattern. All libraries allow to set a solid pen pattern (usually default pattern) and some of then an empty pattern (invisible :) ).

The next usual pen property is color. Every library allows to set any custom pen color.

Geometric pens usually allow to set the style of end caps and the style of the joins between lines. The end cap can be: Round, Square and Flat. Look at the picture - they are placed in this order. When end caps are round - last points are drawn round, when end caps are square - last points are drawn square. When end caps are flat - last points are not drawn at all.
The join style can be: Round, Bevel and Miter. Look at the picture - they area placed in the same order.
Lazarus is using different libraries and each of them has a different pen implementation. For example Carbon does not supports cosmetic pens. And gtk does not support flat end caps for geometric pens (a similar style exists - GDK_CAP_NOT_LAST but it behaves as GDK_BUTT for geometric pens).

2. What was added and how it behaves

Before 0.9.27 pens were the same in Delphi and Lazarus. Pens were (and are) implemented through TPen class and had the following properties: Color, Style, Width (which cannot be less than 1). In order to create a widgetset object, TPen used a call to the CreatePenIndirect winapi compatibility function.

Thus I looked at how winapi works with all those end caps, join styles, cosmetic/geomtric and found another function which can create all what I need - ExtCreatePen. So the first thing I've done was inclusion of ExtCreatePen into LCL. I tried different pens by assigning that function result to Canvas.Pen.Handle. ExtCreatePen allows to choose whether your pen be cosmetic or geomtric, set a predefined line style or use custom dashes, set a solid color or even any brush to draw a line, set end cap style and join style. Don't expect full winapi compatibility from LCL, if you try to use this function directly. Only solid colors are supported - thus you need to pass BS_SOLID for the brush style and hatch = 0.

Another winapi compatibility function I touched was GetObject. It returns a description for the given GDI object. I've extended it to return correct TLogPen if a pen was created by CreatePenIndirect and TExtLogPen if a pen was created by ExtCreatePen.

Next I touched the TPen class itself. I've added TPenEndCap, TPenJoinStlye enumerations and TPen.EndCap, TPen.JoinStyle properties. Also I've added TPen.Cosmetic property since TPen.Width cannot be less than 1 (in other case I would use Pen.Width = 0 as cosmetic). So if you set TPen.Width = 1 and TPen.Cosmetic = True - you'll get your cosmetic pen (this is set by default for newly created pens). In all other cases pens are geometric. Pen is always geometric if Pen.Width > 1 even if Pen.Cosmetic = True. I've also added TPen.SetPattern method to set user defined patterns. If Pen.Style = psPattern lines will be drawn with dashes defined in the pattern array (first number means dash length, second space length and so forth).

3. Limitations

As I wrote before, Carbon does not support cosmetic pens and Gtk has its own point of view about Flat end caps :) But is this all you need to keep in mind?

When I wrote my test application and launched it on Mac OS X I've found that anti aliasing is not always a good friend :) My dashes were invisible and the pen looked almost solid for all styles when its width was equal to one. To eliminate this defects I've added TCanvas.Antialiasing property and now we can control it (only on Carbon and Qt though).

4. Conclusion

Almost all pen capabilities you can find in different libraries are now present in the LCL TPen class. If you missed ExtCreatePen or GetObject winapi functions - you can use them now too. If you don't understand something or just need more info then read msdn (ExtCreatePen), Qt help (QPen class reference) and Gtk reference manual (Graphics Contexts). If you want to look at TPen extensions yourself then launch our "Pen and Brush" example (I was inspired by a Qt Basic Drawing example when wrote it).

Tuesday, November 18, 2008

Compressed HTML help (CHM) in Freepascal

What are CHM files?

CHM files are html archives that are compressed using the LZX compression algorithm.

The Idea

In early 2000 something, there was a discussion about Lazarus and making the help system better. Many different ideas were thrown around but the one that caught my eye was CHM, to me it made sense.

So I began to search for a way to make Lazarus help available as CHM files. The Freepascal documentation: the Run Time Library(RTL), Free Component Library(FCL) and the Lazarus Documentation Lazarus Component Library(LCL) were and are, all available in html format either to download as a zip file or from a website for browsing. This was good because CHM's are just html files in a special archive so in theory it's easy to make CHM's from the existing HTML files.

A bunch of History

I used the MS HTML Help Workshop to make some CHM's from the existing html files. This worked just fine but already I was seeing a problem: CHM readers only exist for Windows.

I found a library, libchm, which was capable of reading and decompressing CHM files. I started working with that and in not too long I was able to extract any file from within a CHM file and display it. This was wonderful but still it was not a perfect solution since Lazarus, if it provided a CHM help system, would rely on an uncommon library.

So I searched using google for an explanation of the CHM file format and found a very detailed specification. http://www.speakeasy.org/~russotto/chm/
The real work began. I converted the LZX decompression code in chmlib from C to Pascal. At first it did not work, but with the help of Micha Nelissen I corrected the mistakes from the conversion and that was finished.

Next came the part which required actual understanding of the innermost workings of a CHM archive. This is when I realized that reading CHM files was not just one file to understand but many. I'll explain: We already know that a CHM file is an archive that contains compressed HTML documents, but how do we know which HTLM documents are included and where they are, or how big they are? CHM's have a document index which uses a tree structure to store information about the directory and files within. After some time I was able to read this structure and see a list of all files and directories in CHM's. Subsequently I realized that a CHM has files stored within it that are added when it is made, which contain information about the Table of Contents, Index, which file is the “Home page” of the CHM and many other disinteresting but important things. Understanding a CHM involves understanding these internal files of a chm.

The specification I referred to above had all the information I needed about reading a CHM's contents but not how to understand these other internal files. Google to the rescue. I found another website http://www.nongnu.org/chmspec/latest/ that had information about the internal files! Not much later I was able to open, extract, and show correctly chm files. All without any external library dependencies.

I was very pleased but the CHMs made by the MS HTML Workshop did not have a very helpful Table of Contents or Index. Additionally there was some discomfort on the part of Lazarus developers that the documentation could only be compiled on Windows PC's since many of the Lazarus developers only used Linux.

This is why several months later, I began work on a CHM compiler! During a two week period I converted more C code to Pascal so a LZX compressor existed for pascal, and wrote from scratch several units to write CHM files.

Now it is possible to write and read CHM's files. The code is included with Freepascal in the packages/chm folder. Also fpdoc the Freepascal documentation tool incorporates the ability to write CHM files.

A program was made (LHelp) and a package made for Lazarus to integrate CHM help. It did not gain popularity however. Why? I'm not sure but it could be because people didn't know it was there. It required setting up in the Lazarus which may have been too compilicated for some. But regardless two years go by.

Recent Progress

I had recently been distracted away from Lazarus development by life and not paying much attention to it's progress. Then an email on the mailinglist caught my eye. A Freepascal dev, Marco van de Voort, was working on integration CHM help into the FP ide. I immediately started reading the logs to find out what interest had been shown in CHM's.

So in the last few weeks I implemented something I had wanted to but as yet, not been motivated to do: I implemented creating and reading the search index CHM's can contain.

What does this mean? This means that now it is possible to author searchable CHM files in any os/platform that FreePascal supports and also read them, all with Pascal code and with no external library requirements.

In the not too distant future, Lazarus may by default use these CHM files to display help when you need it.