Hello Andrea,
thank you for working to improve McCLIM!
Andrea De Michele writes:
Dear all,
I'm working on CLIM-PDF backend and I solved some problems like the text direction in landscape orientation, but I don't like the solution so I didn't submit any PR.
CLIM-PDF backend is a prototype at best. It still has numerous rough edges. Another person who works on improving it is a github user @admich (he sometimes drops on IRC channel too, I don't his real name).
Instead I write here to have some feedback from you about some issues that I have found, some very PDF specific other in the very core of McCLIM.
I'm start from the more pdf specific (and easier) to the more general one (and harder):
- WITH-OUTPUT-TO-PDF-STREAM macro:
The PDF backend is used through WITH-OUTPUT-TO-PDF-STREAM that is based on the analogous macro for Postscript backend defined in the Spec.
I think it is better to define for PDF backend a WITH-OUTPUT-TO-PDF-FILE with a string or a pathname as argument instead of a stream. In this way the user don't have to open the file stream (the user needs also to known that the stream must be open with keyword :element-type '(unsigned-byte 8)). If we remove the WITH-OUTPUT-TO-PDF-STREAM McCLIM can remove the dependency to FLEXI-STREM system.
Macro lambda list is ((stream-var file-stream &rest options) &body body)
Word "stream" in WITH-OUTPUT-TO-PDF-STREAM refers to STREAM-VAR, because that's on what you call the drawing operations, i.e
(with-output-to-pdf-stream (stream file-stream) (draw-rectangle* stream 0 0 10 10))
Same thing goes for PostScript backend. I don't see a problem if you write a PR which makes invoke-with-output-to-pdf-stream accept file-stream as a string or pathname and then open the stream automatically. That will be somewhat DWIM-y approach which doesn't change the interface. In that case I'd like to see a similar change proposed for function implementing:
- with-output-to-ps-stream - with-output-to-raster-image-stream
- GRAFT in PDF and Postscript Backend
PDF and Postscript Backend define their GRAFT but then never use it. The stream sheet where the output is drawn is not a child of a graft. I think this is not correct.
Graft purpose is twofold: accessing a device properties and posing as the display server "root window". The latter doesn't make sense for output-only backends because there is no root window and windowing substrate doesn't apply to them.
Having graft implementation for PDF and PS allows i.e to say, at which millimeter is a middle of the sheet of paper. Whether it is implemented correctly is another story. I think that (graf pdf-stream) should return the graft.
- GRAFT in general
Now each backend define its GRAFT. I think that the backend could only initialize a standard-graft with the right information (in MAKE-GRAFT generic) like: mirror, width, height, device-millimiter-density, backend-transformation, and leave all the other stuff in the common graft module "Core/clim-basic/windowing/grafts.lisp"
Currently our grafts are stubs which allow converting physical sizes to pixels and vice versa, but in principle they have a more profound meaning for output operations with different coordinate systems. I've started exploring this topic with a console backend (interactive), which is different enough to expose many interesting implications.
My current understanding is as follows (I'll use a console example):
- device coordinates are specified by a column and a row - sheet user coordinates are specified by x and y in pixels
Now let's draw something on a sheet:
(draw-rectangle* sheet 0 0 100 100)
MEDIUM-TRANSFORMATION is used to transform user coordinates to device coordinates. To be able to construct such transformation you need to know how many horizontal pixels matches one column, how many vertical pixels matches one row, and if there is some translation between both (i.e pixels start at 0,0 while console starts at 1,1). One way to do that is:
(defun make-px-to-ch-transformation (graft) (let* ((sx (/ (clim:graft-width graft :units :glyph) (clim:graft-width graft :units :pixel))) (sy (/ (clim:graft-height graft :units :glyph) (clim:graft-height graft :units :pixel))) (dx 1) (dy 1)) (clim:compose-translation-with-transformation (clim:make-scaling-transformation sx sy) dx dy)))
which would initialize the sheet's medium when created. Moreover, we could imagine, that sheet user coordinates have no graft representation (i.e they are a density independent pixels), in that case sheet-native-transformation is initialized to transform dp to px, and then medium-transformation is composition of the sheet-native-transformation and a result of the above function.
Of course none of this is currently implemented, but I hope it gives you an idea why graft's are a) useful, b) in case of non-pixel-based devices they are essential.
- transformation and region machinery
Is it the transformation and region machinery correct? Or it is works only in some standard situaions? For example I think that we could obtain a Zoom effect simply by: (setf (sheet-transformation some-sheet) (make-scaling-transformation 1.5 1.5)) but this doesn't work on McCLIM (I try it in the listener demo of clim-tos and there it works). Follow some more specific topics:
I did not investigate how the zooming effect could be conformingly achieved, but I think that modifying the SHEET-TRANSFORMATION is not the way to do that. If I were speculating I'd be more inclined to tinker with the medium transformation. No guarantees it will work (even if it is allowed by the spec which I'm not sure it is).
4a) Setting sheet-native-transformation:
McCLIM set directly the SHEET-NATIVE-TRANSFORMATION with %%SET-SHEET-NATIVE-TRANSFORMATION. I think this is not correct the sheet-native-transformation must be computated and only cached never set directly, otherwise you can lost the parent trasformation.
It is an internal interface which makes possible to update a native transformation when we update the mirror geometry. You are right that it should never be used to change the native transformation, it is only used to update it when it changes (when we know what we are doing). %% prefix means "dangerous".
4b) sheet-native-transformation for basic-sheet:
(defmethod sheet-native-transformation ((sheet basic-sheet)) (with-slots (native-transformation) sheet (unless native-transformation (setf native-transformation (if-let ((parent (sheet-parent sheet))) (compose-transformations (sheet-native-transformation parent) (sheet-transformation sheet)) +identity-transformation+))) native-transformation))
it's not correct because didn't take in account the mirror transformation if the mirror of sheet it is not the same of the parent. So we need to correct this or to add a method for mirrored-sheet-mixin. Of course we didn't see the error because often McCLIM set directly sheet-native-transform as written before in 4a)
Mirrors have little to do with native transformations. See below.
As far as sheet is concerned there are three coordinate systems which do not always match (i.e because of mirroring):
- local - coordinates of a current drawing context - user - coordinates of a sheet - native - coordinates of a graft (screen) - device - coordinates of a medium
As we know in the sheet hierarchy we may have multiple mirrors. Let's consider a very simple hierarchy where each next sheet is a child of the previous one:
graft - (mirror sheet1) - sheet2 - (mirror sheet3) - sheet4
Now some shortened definitions:
SHEET-TRANSFORMATION "Returns a transformation that converts coordinates in the sheet sheet's coordinate system into coordinates in its parent's coordinate system."
SHEET-NATIVE-TRANSFORMATION "Returns the transformation for the sheet sheet that converts sheet coordinates into native coordinates."
SHEET-DELTA-TRANSFORMATION "Returns a transformation that is the composition of all of the sheet transformations between the sheets sheet and ancestor."
SHEET-DEVICE-TRANSFORMATION "Returns the transformation used by the graphics output routines when drawing on the mirror."
-----------------------------
For illustration purpose let's assume that sheet4's transformation is a translation [20,20] (layout panes do that to position their children) and that sheet3's transformation is translation [100,100]. Other sheet have the +IDENTITY-TRANSFORMATION+.
Point [0,0] in sheet4 "user" coordinates is: - [20,20] in sheet3 "user" coordinates - [120,120] in sheet2 "user" coordinates - [120,120] in sheet1 "user" coordinates - [120,120] in graft "user" coordinates
Since graft represents a screen, its "user" coordinats are native coordianates. We completely ignore mirrors when we compute SHEET-NATIVE-TRANSFORMATION, because it is a jump from sheet4 to the graft. That means, that the method mentioned by you earlier is correct.
Delta transformation is a bit more interesting, because it allows to pick another ancestor (than a graft) to whose "user" coordinates we want to transform "our user" coordinates.
Device transformation is the most confusing though, because mirror may have its local (in display server terms) coordinates which do not coincide with native coordinates.
Point [0,0] in sheet4 "user" coordinates is: - [20,20] in sheet3 "device" coordinates
Point [0,0] in sheet3 "user" coordinates is: - [0,0] in sheet3 "device" coordinates
4c) What is sheet-native-region?
From the spec: "Returns the region for the sheet sheet in native coordinates". This is not clear. The first time that I read it I understood that it is the sheet-region of the sheet transformed in native coordinates, that means for examples that a sheet with a region +everywhere+ have a native-region +everywhere+. Instead for McCLIM, and clim-tos too, it is the region of the mirror "for" the sheet i.e. the sheet-region of the sheet transformed in native coordinates clipped by the mirror region. This interpretation maybe is correct because the spec said about sheet-device-region:
"Returns the actual clipping region to be used when drawing on the mirror. This is the intersection of the user's clipping region (transformed by the device transformation) with the sheet's native region."
Anyway, in this case, the sheet-native-region for the graft in Mcclim is wrong: it is +everywhere+
SHEET-NATIVE-REGION is the sheet region in screen coordinates. Graft *is* the screen, so we simply use sheet-region. Child native region is always clipped by its parent native region (parent is a "window" into a deeper plane), so we use region intersections between parent and child native regions.
Things once again get more interesting for device-region. As descibed in the spec, it is the sheet-native-region clipped by the mirror region. Notice, that device-region may be partially obscured by its parent (however in practice it works poorly unless the parent is also a mirror, you may experiment with hierarchy tool demo in clim-examples with random mirroring arrangement - resize parent's for different combinations parent-child mirror-notmirror).
-----------------------------------------
I hope that it clarifies at least a little what is going on. I might have made a mistake somewhere because it was a long day and I'm looking into my notes from around 6 months ago :) and had to reread multiple parts of the spec.
Best regards, Daniel
-- Daniel Kochmański ;; aka jackdaniel | Przemyśl, Poland TurtleWare - Daniel Kochmański | www.turtleware.eu
"Be the change that you wish to see in the world." - Mahatma Gandhi