I have never been entirely comfortable with this - I believe that we shouldn't vary the OutputDevice class, but instead the functionality should be implemented in a command pattern. In a command pattern, you use an object to encapsulate the functionality used to perform an action. What this means is that OutputDevice no longer needs to know how to directly draw a line, pixel, rectangle or any other primitive we throw at it - this is all done in the command object. I call these OutputDevice Drawables. It turns out, I find it easier to test a command object.
In fact, the more I rewrote code to use the command pattern, the more I found that we were using a particular sequence of actions in most of our drawing functions:
- Add action to GDI metafile
- Can we draw? No - exit
- Initialize the clipping region if possible
- Initialize the object's color
- Initialize the object's fill color
- Acquire the SalGraphics instance
- Do the actual drawing!
- Paint alpha virtual device
You can see the base work for this in a gerrit patch I submitted today. To use the actually command pattern is very easy: if there is not an existing function like DrawPixel in OutputDevice, you just have to invoke the command object via OutputDevice::Draw(Drawable* pDrawable). An example can be found in the unit test.
To implement a new command, you just derive from Drawable - the simplest Drawable class you can define only needs a constructor with the parameters you would normally send to a Draw command in OutputDevice (e.g. PixelDrawable only needs a Point sent to the constructor), and you implement the actually drawing in DrawCommand(OutputDevice* pRenderContext).
A few things I found helped me when I created Drawables - I quite like passing parameters to functions, I'm not a huge fan of a lot of state in the class. Drawables work because they essentially work by calling the Execute function, this handles all the basic drawing for you on the state you set in the constructor. I have found in some patches that I have on github that the functionality is somewhat complex, for this I have extract function to simplify test and readability of the code (an example of this is GradientDrawable - see GradientDrawable.cxx and GradientDrawableHelper.cxx).