Hello Daniel,
yes, I'am @admich but the pronouns that you have used is right: in Italy Andrea is a male name.
Follow some comments, in particular I think that what you wrote about transformations is wrong.
Daniel Kochmański daniel@turtleware.eu writes:
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
Ok so your suggestion is to modify the actual WITH-OUTPUT-TO-PDF-STREAM macro without introduce new WITH-OUTPUT-TO-PDF-FILE macro, I will try to do it.
- 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).
Why not? If you change the medium-transformation instead of sheet-transformation you can not obtain zoom effect because in the replay of output-record the medium-transformation is not take in account. The Spec. (16.2) says for replay-output-record:
"Displays the output captured by the output record record on the output recording stream stream, exactly as it was originally captured (subject to subsequent modifications). The current user transformation, line style, text style, ink, and clipping region of stream are all ignored during the replay operation. Instead, these are gotten from the output record."
The "user transformation" *is* the medium-transformation as stated in the Spec (10.1):
" medium-transformation medium [Generic Function]
The current user transformation for the medium medium. This transformation is used to transform the coordinates supplied as arguments to drawing functions to the coordinate system of the drawing plane. See Chapter 5 for a complete description of transformations. The :transformation drawing option temporarily changes the value of medium-transformation. "
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".
the sheet-native-transformation method already have the caching mechanism. I think that the only things to do when we update the mirror geometry is to invalidate-cached-transformations and invalidate-cached-regions. The next call of sheet-native-transformation will recompute it.
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
I think this is not right respect to Spec and certainly is not what McCLIM does. Try yourself open a clim-listener move the window in some place of the screen and try: (sheet-native-tranformation (frame-top-level-sheet *application-frame*)) I always have the identity transformation as answer.
Check also this schema in Core/clim-basic/windowing/mirrors.lisp:
;;; ;;; NT NT = native transformation ;;; sheet ----------------> mirror PNT = parent's NT ;;; | | MT = mirror transformation ;;; | | T = sheet transformation ;;; | | ;;; T | | MT ;;; | | ;;; | | ;;; | | ;;; v PNT v ;;; parent ----------------> parent ;;; mirror ;;;
Seems clear that sheet-native-transformation is the transformation from sheet coordinate to mirror coordinate and not to the graft coordinate as you stated. Infact the spec. introduce sheet-native-transformation in the chapter about the mirrored sheet.
Here how I understand the Spec.:
1) each sheet have a coordinate system. sheet-transformation transform the coordinate of the sheet to the coordinate of its parent (we agree on this)
2) for each sheet that it is mirrored (it is not necessary that is directly mirrored) sheet-native-transformation transform the coordinate of the sheet to the native coordinate that are used from the backend drawing functions (e.g. for clx xlib:draw-line, xlib:draw-arc, ....) i.e. the coordinate of the mirror.
3) the coordinate in clim drawing-functions are the coordinate in the medium coordinate system that can be different from the sheet coordinate because you can change it with drawing option or with macro like with-scaling, with-translation, .... . The transformation from medium coordinate system to sheet coordinate system is called in the spec. (I said this before) "user transformation", and sheet-device-transformation is the composition between user transformation and the sheet transformation, so is the transformation that transform the coordinate used in the clim drawing functions to the coordinate used in the backend native drawing functions.
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.
Again what you said is not true: the sheet-native-region of a frame-top-level-sheet ever report a region starting from 0 0.
And I continue to be not sure of Spec. interpretation, there are two (at least) possibilities:
A) sheet-native-region return the sheet-region of the sheet transformed in native coordinates.
B) sheet-native-region return the sheet-region of the sheet transformed in native coordinates clipped by the mirror region.
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