From: Tamas Papp
Subject: how to handle "new" scalar type
Date: 
Message-ID: <87ps2h7t44.fsf@pu100877.student.princeton.edu>
I the library I am working on, the user may give coordinates/distances
as a scalar (which will be intepreted by the underlying cairo
interface), or as a relative unit "em", which is the width of the
letter m.

I am trying to find a way to represent this conveniently.  I thought I
would wrap it in a struct:

(defstruct em value)

(defun make-canvas (width height)
  (let ((width (etypecase width
                 ((em) #|... extract em-value, convert ...|#)
                 ((double-float) #|... use it directly ...|#))))
    ;; do stuff
  ))

and use it like this:

(make-canvas (make-em 3) 400)

Is there a smoother way?  Wrapping it in a struct seems overkill.

Tamas

From: Pascal Bourguignon
Subject: Re: how to handle "new" scalar type
Date: 
Message-ID: <87ps2hbz4r.fsf@voyager.informatimago.com>
Tamas Papp <······@gmail.com> writes:

> I the library I am working on, the user may give coordinates/distances
> as a scalar (which will be intepreted by the underlying cairo
> interface), or as a relative unit "em", which is the width of the
> letter m.
>
> I am trying to find a way to represent this conveniently.  I thought I
> would wrap it in a struct:
>
> (defstruct em value)
>
> (defun make-canvas (width height)
>   (let ((width (etypecase width
>                  ((em) #|... extract em-value, convert ...|#)
>                  ((double-float) #|... use it directly ...|#))))
>     ;; do stuff
>   ))
>
> and use it like this:
>
> (make-canvas (make-em 3) 400)
>
> Is there a smoother way?  Wrapping it in a struct seems overkill.

You have basically two options.  
Either keep it symbolically, or keep it numerically.

That is, either you keep track of the ems from the input to the
output, and convert to pixels at the output,  or you convert the ems
into pixels at the input.


Converting at the input is easy:

(defparameter *em* 18 "Size of an em")
(defun make-em (x) (* x *em*))

and allows you to use the normal numeric operators:

  (+ (make-em 3) 4)

but the problem is that the *em* size which is taken is the one that
was set at input-time, perhaps not when you need it.

We have a similar problem with time "units" which are also of variable
length, like months and years (and depending on definition days and
weeks too).  If you want to convert them to seconds and deal only with
universal-times and second, you break because one month from the 15 of
February is not the same as one month from the 15 of March (and the
difference is not always the same).



So you probably want to keep it symbolically, and using a structure is
not a bad idea, since you get for free a lot of niceties, like a
unique type.  You cannot confuse ems with apples, even if both have
only one slot of the same type.

(defstruct em    width)
(defstruct apple width)

(em-to-pixels (make-apple 18)) --> error


Normally, structures are stored rather efficiently, as a vector, and
using only one additionnal (invisible) slot, to store the type.  So
spacewise, a cons and a struct of one slot should not differ much.
There's not much overkilling here...


On the other hand, you may have to develop a symbolic arithmetic
package to be able to operate on ems (and is and xs and ens, and
pixels, and cms, etc)

 (typo+ (em 3) (pixel 40) (cm 3)) --> #<some typographical length>
 (typo* 3 (typo+ (em 3) (pixel 40) (cm 3))) --> #<triple the previous>
 ;; etc.

You only need a final function to convert these compound symbolic
measures into pixels (or cm or whatever you need) at the end.



Of course, you also need to introduce some more abstraction!
Instead of using etypecase in make-canvas and everywhere, write:


(defclass canvas ()
  ((width  :initarg :width  :reader width  :type typo-length)
   (height :initarg :height :reader height :type typo-length)
   ...))

(defclass pixel-canvas (canvas)
  ((x-resolution :initarg x-resolution :documentation "In pixel/inch" 
                 :reader x-resolution)
   (y-resolution :initarg y-resolution :documentation "In pixel/inch"
                 :reader y-resolution)
   ...))

(defmethod initialize-instance :after ((self pixel-canvas) &key &allow-other-keys)
   (let ((pixel-width  (* (x-resolution self) (typo-to-pixel (width  self))))
         (pixel-height (* (y-resolution self) (typo-to-pixel (height self)))))
     (setf (pixels self) (make-pixmap pixel-width pixel-height))
     ...)
   self)

So you can use any typographical length unit to create your canvases:

(make-instance 'pixel-canvas :width (em 30) :height (cm 3)
                             :x-resolution 300 :y-resolution 300)

and you may even create vectorial or other kind of canvases.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

5A"Logiciels libres : nourris au code source sans farine animale."
From: Tamas Papp
Subject: Re: how to handle "new" scalar type
Date: 
Message-ID: <87ir897qnq.fsf@pu100877.student.princeton.edu>
Pascal Bourguignon <···@informatimago.com> writes:

> ...
> That is, either you keep track of the ems from the input to the
> output, and convert to pixels at the output,  or you convert the ems
> into pixels at the input.
> ...

Dear Pascal,

Thanks for the very detailed answer, it helped a lot.  The size of an
em is context-dependent, and I need to convert it where I use it, so I
will use structs.

Tamas