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.
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):
1) 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.
The difference from PDF to Postscript is giustified because PDF is a file format so the output is ever a file, instead the Postscript output can be send on a stream directly atttached to a postscript printer.
We can also add a WITH-OUTPUT-TO-POSTSCRIPT-FILE macro without remove the WITH-OUTPUT-TO-POSTSCRIPT-STREAM defined in the Specification.
2) 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.
3) 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"
4) 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:
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.
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)
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+
Hi Andrea,
See comments below. Thanks.
On 1/18/20 7:46 AM, Andrea De Michele wrote:
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.
I didn't realize this was broken, but looking forward to seeing a solution. Can you make a github "issue" for this?
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.
Why not have both? Aren't there cases where a user might want to write a PDF to a stream (e.g. a web server serving up a dynamic PDF)?
The difference from PDF to Postscript is giustified because PDF is a file format so the output is ever a file, instead the Postscript output can be send on a stream directly atttached to a postscript printer.
We can also add a WITH-OUTPUT-TO-POSTSCRIPT-FILE macro without remove the WITH-OUTPUT-TO-POSTSCRIPT-STREAM defined in the Specification.
Yes, I think that's what I'm suggesting.
- 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.
This sounds plausible but I would look for, e.g., jackdaniel to weigh in on grafts here.
- 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"
Again, sounds plausible, even nice!
- 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:
Yes, this is definitely an area that could use more testing and, likely, bug fixing. These sorts of things _should_ work.
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.
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)
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+
Yes, lots to discuss here. Perhaps opening up "issues" for each of the specific problems would be good?
Thanks for your attention to the PDF backend in specific and McCLIM in general. One major limitation of the PDF backend is that we currently don't support proper unicode fonts. It appears that this is a fair amount of work, but I view this as a necessity to have a truly working PDF backend. Do you have any experience with this?
Thanks again,
Cyrus
Hello Cyrus,
thanks for your answers. As you suggested I will open some issues on github.
For the unicode font, I'm sorry but I don't have any experience.
Cyrus Harmon ch-mcclim@bobobeach.com writes:
Hi Andrea,
See comments below. Thanks.
On 1/18/20 7:46 AM, Andrea De Michele wrote:
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.
I didn't realize this was broken, but looking forward to seeing a solution. Can you make a github "issue" for this?
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.
Why not have both? Aren't there cases where a user might want to write a PDF to a stream (e.g. a web server serving up a dynamic PDF)?
The difference from PDF to Postscript is giustified because PDF is a file format so the output is ever a file, instead the Postscript output can be send on a stream directly atttached to a postscript printer.
We can also add a WITH-OUTPUT-TO-POSTSCRIPT-FILE macro without remove the WITH-OUTPUT-TO-POSTSCRIPT-STREAM defined in the Specification.
Yes, I think that's what I'm suggesting.
- 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.
This sounds plausible but I would look for, e.g., jackdaniel to weigh in on grafts here.
- 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"
Again, sounds plausible, even nice!
- 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:
Yes, this is definitely an area that could use more testing and, likely, bug fixing. These sorts of things _should_ work.
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.
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)
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+
Yes, lots to discuss here. Perhaps opening up "issues" for each of the specific problems would be good?
Thanks for your attention to the PDF backend in specific and McCLIM in general. One major limitation of the PDF backend is that we currently don't support proper unicode fonts. It appears that this is a fair amount of work, but I view this as a necessity to have a truly working PDF backend. Do you have any experience with this?
Thanks again,
Cyrus
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
Daniel Kochmański writes:
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).
My apologies (for both a mistake and a wrong pronoun), I've just realized it is you. Time to go to sleep!
-- 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
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
Hey Andrea,
Follow some comments, in particular I think that what you wrote about transformations is wrong.
I'm pretty certain about my interpretation of how mediums behave (it is reinforced by hours of tinkering with the console backend). As of sheets and their transformations -- I think it that the topic is underspecified; it might be that my understanding is lacking and/or incorrect. IMO it is important to keep a separation between mediums and sheets in mind. That is: medium is an entity on its own rights and the sheet interface is built on top of it (that is when interpeting mediums we should not into account sheet specification).
- with-output-to-pdf-stream
- 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.
Preciesly. Thank you.
- 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. "
10.1 talks about drawing on the sheet. medium-transformation transforms coordinates supplied to the drawing function to coordinates of the mirror on which we draw (putting aside the question that are device coordinates or native coordinates). When drawing operation is performed on the output recording stream, then medium-transformation is recorded and then it is reinstationed when we replay the output record (so the current transformation is indeed ignored).
(with-drawing-options (stream :transformation *drawing-transformation*) (draw-rectangle* stream 0 0 10 10))
(with-drawing-options (medium :transformation *ignored-transformation*) (replay stream))
That does not mean that replaying output records is done with the medium-transformation set to +identity-transformation+. It means, that the transformation from the *time of drawing* is used. So when we replay the output record, medium-transformation is *drawing-transformation*. Same goes for other recorded medium components. What exactly is recorded depends on the output record class.
It is implemented as an around method on replay-output-record specialized on gs-transformation-mixin. There is a catch though: basic-medium eagerly transforms coordinates, so backends are not expected to apply the medium-transformation to coordinates (unless their medium does not inherit from the basic-medium[1]). That's why zooming is not easy to achieve. That's also why hardware transformations would require custom medium. I'm positive though that both could be implemented (and zooming would be easier).
[1] "An :around on basic-medium for each of the drawing functions will have already transformed the user coordinates to medium coordinates before the most specific, implementation-dependent method is called." -- 12.7.2 Medium-specific Drawing Functions.
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.
You are right that invalidating cache would be correct. %%set-sheet-native-transformation is used as an optimization. i.e docstring of update-mirror-geometry which uses it:
"This function reflects the current sheet region and sheet transformation to the mirror. It also sets up the native transformation. This function is supposed to be called whenever one of the following happens:
- the sheet's transformation changed - the sheet's region changed - the parent's native transformation changed - the parent's transformation changed - the parent's mirror region changed
Also if the sheet's native transformation changes, the mirror's contents need to be redrawn, which is achieved by calling PORT-DIRTY-MIRROR-REGION. TODO is this true?
Since changing the sheet's native transformation might thus be expensive, this function tries to minimize changes to it. (although it does not try very hard)."
This writer method is also used in a macro which allows swapping mirrors (which is an ugly hack w which should go!).
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.
Thank you for elaborating on parts. You've certainly convinced me that my rationale about transformations has holes, I'm not sure what is the right behavior anymore. If you create an issue on github we may move he discussion there. Before we move forward with code changes we need to get a common understanding of which coordinate system does what. I need to do some reading on my part (most notably 8, 10 and 12 chapters of the spec), but currently we have some open PRs and I don't think I have enough energy to jump at that now.
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