[main]Notes on TeXmacs

Modular graphics with Scheme

This post is a part of a series of Scheme graphics in TeXmacs. Other posts in the same series are Composing TeXmacs graphics with Scheme and Embedding graphics composed with Scheme into documents; we also plan to write a post on how to generate Scheme graphics with external files.

Like in the other posts of the series, we assume that the reader is familiar with simple Scheme syntax. We link again to the Wikipedia book Scheme programming and to Yet Another Scheme Tutorial by Takafumi Shido as two possible web resources for learning Scheme.

TeXmacs graphics provide a set of elementary objects (points, polylines, splines and so on). It would be nice to have at hand functions to deal with complex objects, which can be seen as unions of elementary objects: for example, a triangle in a half-circle is the union of a polyline (to represent the triangle) with the half-circle, which in turn is the union of a polyline (the diameter of the half-circle) and an arc.

We would like to name complex objects and to transform them as units: for example translate them or change the line style or the color for all of the object components; naming complex objects will make our code clearer when we deal with them—and for example we use them as building blocks of even more complex objects.

In TeXmacs there is a partially-implemented interface for dealing with complex graphics objects (see HelpScheme extensionsScheme interface for the graphical mode and the .scm files in the progs/graphics/ directory in the TeXmacs distribution). In particular, graphics can be grouped using the primitive gr-group.

In this post, we will develop a small interface of our own; we will show how to compose and use in a drawing complex graphical objects, how to shift them and how to set properties of already-existing complex objects.

1.Composing complex objects

In Scheme, a natural way of composing complex objects is joining the building blocks into a list. To get TeXmacs to draw the objects represented by our (possibly nested) lists, we need to transform them into lists that conform to the TeXmacs graphical input.

The input for graphics in TeXmacs Scheme is a list which starts with the symbol graphics, whose elements are elementary graphical objects. Each elementary graphical object is a list, which starts with one of these symbols: point, line, cline, spline, cspline, arc, carc, text-at, math-at, document-at, and can be optionally wrapped in a list starting with the symbol with that sets properties.

One possible way to turn an arbitrarily nested list of elementary objects (a complex object, that is) into the flat list of lists that TeXmacs expects is flattening it recursively, stopping the recursive flattening every time we find one of the symbols that indicate the start of a graphical object (including the symbol with).

The following group of three functions does that. The main function is adapted from one of the answers to this Stack Overflow question.

Flattening nested lists of graphical objects

denest-test is a test to stop denestification when one finds one of the symbols in the list objects-list or the symbol with.

We build it up from an object-test, which check membership in object-list. object-test itself will help later too, in a function that applies properties to all of the elementary components of an object.

Scheme]
(define objects-list '(point line cline spline arc
                    carc text-at math-at document-at))
Scheme]
(define (object-test expr)   (not (equal?
        (filter (lambda (x) (equal? x expr)) objects-list)
        '())))
Scheme]
(define (denest-test expr)
  (or
   (object-test expr)
   (equal? expr 'with)))

denestify-conditional flattens a list recursively, stopping the recursion if it meets one of the symbols in objects-list or the symbol with.

denestify-conditional is not tail-recursive; we write it in this way as it is simpler than the tail-recursive version and it will work fairly, as in these examples we will apply it to short lists.

A tail-recursive version of the function is given in another answer to the same StackExchange question which we used as starting point.

Scheme]

;; start from the answer https://stackoverflow.com/a/33338401 
;; to the Stack Overflow question
;; https://stackoverflow.com/q/33338078/flattening-a-list-in-scheme

;;(define (denestify lst)
;;  (cond ((null? lst) '())
;;        ((pair? (car lst))
;;         (append (denestify (car lst))
;;                 (denestify (cdr lst))))
;;        (else (cons (car lst) (denestify (cdr lst))))))

;; This function is not tail-recursive; 
;; see https://stackoverflow.com/a/33338608
;; for a tail-recursive function
  (define (denestify-conditional lst)
    (cond ((null? lst) '())
          ((pair? (car lst))
           ;; If the car of (car lst) is 'with or another
           ;; of the symbols in denest-test, we cons it
           (if (denest-test (car (car lst)))
               (cons (car lst)
                     (denestify-conditional (cdr lst)))
               ;; otherwise we flatten it with recursion,
               ;; obtaining a flat list, and append it to
               ;; the flattened rest of the list, in this
               ;; way flattening the combination of the
               ;; two lists
               (append (denestify-conditional (car lst))
                       (denestify-conditional (cdr lst)))))
          ;; (car lst) is an atom
          (else (if (denest-test (car lst))
               ;; test presence of (car lst) in the list
               ;; of symbols that stop denestification
                    lst ;; we leave lst as it is
               ;; otherwise we cons (car lst) onto the
               ;; flattened version of (cdr lst)
                    (cons (car lst) 
                          (denestify-conditional 
                             (cdr lst)))))))
Scheme]

Definition of basic graphical objects

Let us define a function for generating points, and use that to define some graphical objects (the same objects of our previous posts). We will then combine these objects in a complex unit and show that TeXmacs draws it using our denestify-conditional function.

Scheme]
(define (pt x y)
  ‘(point ,(number->string x) ,(number->string y)))

We will work with a circle so we need π!

Scheme]
(define pi (acos -1))

Define points for the triangle

Scheme]
(define pA (pt -2 0))
Scheme]
(define pB (pt 2 0))

Then the third point of the triangle, on the circumference, defined with the help of two variables xC and yC:

Scheme]
(define xC (- (* 2 (cos (/ pi 3)))))
Scheme]
(define yC (* 2 (sin (/ pi 3))))
Scheme]
(define pC (pt xC yC))

Finally the points at which we will mark the triangle's vertices with letters:

Scheme]
(define tA (pt -2.3 -0.5))
Scheme]
(define tB (pt 2.1 -0.5))
Scheme]
(define tC (pt (- xC 0.2) (+ yC 0.2)))

Use points to build up a drawing, where each object is typed down as a list.

Scheme] 
(stree->tree
 ‘(with "gr-geometry"     
      (tuple "geometry" "400px" "300px" "center")
    "font-shape" "italic"
    (graphics
      ;; the arc and the line together make the semicircle
      (with "color" "black"  (arc ,pA ,pC ,pB))
      (with "color" "black"  (line ,pA ,pB))
      ;; a closed polyline for the triangle
      (with "color" "red" "line-width" "1pt" 
            (cline ,pA ,pB ,pC))
      ;; add letters using text-at
      (with "color" "black"  (text-at "A" ,tA))
      (with "color" "black"  (text-at "B" ,tB))
      (with "color" "black"  (text-at "C" ,tC))
      ;; finally decorate with the TeXmacs symbol
      (with "color" "blue"  "font-shape" "upright" 
            (text-at (TeXmacs) ,(pt -0.55 -0.75))))))
;; and close all of the parentheses!!!

Combination of individual graphical objects into complex objects

Let us join graphical objects as lists to form complex graphical objects. We will use the conditional flattening inside the graphical list to express our complex objects in the way TeXmacs expects them.

Scheme]
(define triangle ‘(with "color" "red" "line-width" "1pt"
                        (cline ,pA ,pB ,pC)))
Scheme]
(define half-circle ‘(
(with "color" "black" (arc ,pA ,pC ,pB))
(with "color" "black" (line ,pA ,pB))))
Scheme]
(define letters ‘((with "color" "black"  (text-at "A" ,tA))
                  (with "color" "black"  (text-at "B" ,tB))
                  (with "color" "black"  (text-at "C" ,tC))))
Scheme]
(define caption ‘((with "color" "blue"  
                        "font-shape" "upright"
            (text-at (TeXmacs) ,(pt -0.55 -0.75)))))
Scheme] 
(stree->tree
 ‘(with "gr-geometry"
      (tuple "geometry" "400px" "300px" "center")
    "font-shape" "italic"
    ,(denestify-conditional ‘(graphics
                             ,triangle
                             ,letters
                             ,half-circle
                             ,caption))))

A function for complex graphics

To write less ourselves, we can define a function that flattens graphics lists and wraps them with the TeXmacs syntax:

Scheme]
(define (scheme-graphics x-size y-size alignment graphics-list)
  (stree->tree
   ‘(with "gr-geometry" 
        (tuple "geometry" ,x-size ,y-size alignment)
        "font-shape" "italic"
        ,(denestify-conditional 
            ‘(graphics ,graphics-list)))))

Let's use it:

Scheme] 
(scheme-graphics "400px" "300px" "center" ‘(
,half-circle
,triangle
,letters
,caption))

2.Manipulation of complex objects

We would like to manipulate complex objects as units. Let's see how to translate them; one can in a similar way rotate and stretch them (perhaps with respect to reference points which are calculated from the objects themselves). We will then see how to apply properties to all of the components of an object.

Translate complex objects

Since all objects are made out of points (that is, lists that start with the symbol point), we need to translate each point which the list is composed of.

We do it by mapping a translation function recursively

1. Neither the function for translation nor the one for property-setting are tail-recursive, but they are sufficient for our examples. Moreover the stack dimension in the calls to these function is determined by how much lists are nested, which keeps the stack dimension small.

1 onto the list that represents a complex object; in this recursive mapping we distinguish between expressions that represent points (that have to be translated) and expressions that represent something else (that have to be left as they are).

The distinction is made when the recursion either gets to a point or gets to an atom: if it does get to an atom, then the translation function acts as the identity function. Here is the algorithm applied to a list:

A function to translate points

Scheme]
(define (translate-point point delta-vect)
  (let ((coord (map string->number (cdr point))))
    (pt (+ (car coord) (car delta-vect))
        (+ (cadr coord) (cadr delta-vect)))))

The general translation function. It is called translate-element rather than translate-object because it applies to all elements, including atoms.

Scheme]
(define (translate-element element delta-vect)
  (cond ((list? element)
     (if (equal? (car element) 'point)
        (translate-point element delta-vect)
        (map (lambda (x) 
               (translate-element x delta-vect)) 
             element)))
        (else element)))

Let's apply this function to a polyline to see its effect on the Scheme expression:

Scheme] 
(translate-element ‘((line ,(pt 1 2) ,(pt 2 3) ,(pt 3 4))
                 (text-at TeXmacs ,(pt -1 -1))) '(1 1))

((line (point "2" "3") (point "3" "4") (point "4" "5")) (text-at TeXmacs (point "0" "0")))

Scheme]

Let us now use our translation function on a complex object in a drawing: the triangle inscribed in the half-circle we first drew by listing all of the elementary objects and then by combining the complex triangle, half-circle and letter objects (we are then going to add the TeXmacs caption!).

This time, we define the whole drawing as a unit, calling it triangle-in-half-circle; we will need to remember to unquote the symbol triangle-in-half-circle when placing it inside lists defined through quasiquoting.

Scheme]
(define triangle-in-half-circle  ‘(
   ,half-circle
   ,triangle
   ,letters))

Draw it by placing it in the list argument of the scheme-graphics function:

Scheme] 
(scheme-graphics "400px" "300px" "center" 
      ‘(,triangle-in-half-circle ,caption)))

Now add to the drawing a translated copy of triangle-in-half-circle and the TeXmacs caption (translating that too):

Scheme] 
(scheme-graphics "400px" "300px" "center" 
‘( ,triangle-in-half-circle
   ,(translate-element triangle-in-half-circle '(1.0 -1.5))
   ,(translate-element caption '(1.0 -1.5))))

Manipulate object properties

We write a simple function which wraps each elementary object in a with, placing it inside with respect to any other with construct the object might be already placed in. This function, applied a few times, generates deeply nested lists that may be difficult to read; a more refined function would check if the object is already inside a with construct and if it is, modify the with list rather than adding another one. We prefer the simple function to the refined one to keep this post to the point (“modular graphics”).

The with needs to be placed as innermost wrapping construct for each element so that the new property will prevail onto pre-existing properties (withs are scoping constructs).

The apply-property function is built with the same logic as the translate-element function. When applied to a list, it first checks if the list starts with one of the “graphical object” symbols; if it does, apply-property wraps it in a with construct; if it does not, apply-property maps itself onto the list elements. When applied to an atom, apply-property returns the input.

In this way the function seeks recursively inside the lists of all the graphical objects, and applies the desired property to each.

Scheme]
(define (apply-property element name value)
  (cond 
    ((list? element)
        (if (object-test (car element))
            ‘(with ,name ,value ,element)
            (map (lambda (x) (apply-property x name value))
                 element)))
     (else element)))        

Let's apply dashing to the triangle object (let us view the Scheme lists):

Scheme] 
(apply-property triangle "dash-style" "11100")

(with "color" "red" "line-width" "1pt" (with "dash-style" "11100" (cline (point "-2" "0") (point "2" "0") (point "-1.0" "1.73205080756888"))))

We act on a more complex object in the same way as we do on a simpler object—we now apply dashing to the translated copy of triangle-in-half-circle:

Scheme] 
(apply-property
  (translate-element triangle-in-half-circle '(1.0 -1.5))
  "dash-style" "11100")

(((with "color" "black" (with "dash-style" "11100" (arc (point "-1.0" "-1.5") (point "0.0" "0.23205080756888") (point "3.0" "-1.5")))) (with "color" "black" (with "dash-style" "11100" (line (point "-1.0" "-1.5") (point "3.0" "-1.5"))))) (with "color" "red" "line-width" "1pt" (with "dash-style" "11100" (cline (point "-1.0" "-1.5") (point "3.0" "-1.5") (point "0.0" "0.23205080756888")))) ((with "color" "black" (with "dash-style" "11100" (text-at "A" (point "-1.3" "-2.0")))) (with "color" "black" (with "dash-style" "11100" (text-at "B" (point "3.1" "-2.0")))) (with "color" "black" (with "dash-style" "11100" (text-at "C" (point "-0.2" "0.43205080756888"))))))

Placing our objects in the list argument of scheme-graphics, we obtain the drawing in a modular way:

Scheme] 
(scheme-graphics "400px" "300px" "center" ‘(
,triangle-in-half-circle
,(apply-property
  (translate-element triangle-in-half-circle '(1.0 -1.5))
  "dash-style" "11100")
,(translate-element caption '(1.0 -1.5))))

apply-property and translate-element can be applied in both orders. In the previous example we translated first the object, then we applied the dashing; here we apply the dashed style first, then we translate the object.

Scheme] 
(scheme-graphics "400px" "300px" "center" ‘(
,triangle-in-half-circle
,(translate-element   (apply-property
   triangle-in-half-circle   "dash-style" "11100")
  '(1.0 -1.5))
,(translate-element caption '(1.0 -1.5))))

The last application of apply-property prevails, as it is set in the innermost with list; subobjects which have individually-set values of the property will all be set to the value fixed by the last application of apply-property. Here we apply a short-dash style on an object which has long-dashing throughout:

Scheme] 
(scheme-graphics "400px" "300px" "center" ‘(
,triangle-in-half-circle
,(translate-element (apply-property
   (apply-property
   triangle-in-half-circle   "dash-style" "11100")
  "dash-style" "101010")
  '(1.0 -1.5))
,(translate-element caption '(1.0 -1.5))))

3.Scheme expressions that show what we mean

We apply again the idea of modularity by assigning to a variable the object which we obtain after translation and application of dashing, and using the variable to build a drawing. Our code again shows that in Scheme building blocks combine together well.

Scheme] 
(define translated-triangle-in-half-circle-short-dashes
  (translate-element
     (apply-property
       (apply-property  triangle-in-half-circle 
                        "dash-style" "11100")
        "dash-style" "101010")
     '(1.0 -1.5)))

Scheme]
(define translated-caption 
  (translate-element caption '(1.0 -1.5)))

The drawing is made out of complex objects, but the final expression shows what we have in mind: our geometrical construction and a shifted replica drawn with short dashes.

Scheme] 
(scheme-graphics "400px" "300px" "center" ‘(
,triangle-in-half-circle
,translated-triangle-in-half-circle-short-dashes
,translated-caption))

We can play further. Let's blend the triangle inside the half-circle stepwise. Our functions are not sophisticated enough to target a subunit of a complex object: applying a line-width to the whole drawing of the triangle in the half-circle would eliminate the different line-widths for the triangle and arc; for this reason, we use as an example of blending in the triangle alone, which is one of the units we defined.

The blend-in-triangle function shifts the triangle by delta times the vector (1.0 -1.5), applies dashing and a linewidth which is thicker as the triangle is closer to being inscribed in the half-circle (we are going to use this function for values of delta which yield positive values of the line thickness).

Scheme]
(define (blend-in-triangle delta)
  (translate-element   (apply-property
   (apply-property
   triangle
  "dash-style" "101010")
   "line-width" (string-join 
         ‘(,(number->string (- 1 delta)) "pt") ""))
         ‘(,(* 1.0 delta) ,(* -1.5 delta))))

Let's map this function on a list of d values, and let us name the object it returns (all lists of objects will be flattened by the conditional flattener) in a meaningful way:

Scheme]
(define delta-lst
       '(0.2 0.4 0.6 0.8)))
Scheme]
(define blend-in-triangle-series
  (map blend-in-triangle delta-lst))

Here is the triangle blending in into the half-circle ... or fading away!

Scheme] 
(scheme-graphics "400px" "300px" "center" ‘(
,blend-in-triangle-series
,triangle-in-half-circle
,translated-caption))

Scheme]

One could wish for more actions. For example, one could wish to find intersections of lines which define objects, and assign them to new objects. Another example is to define styles as shortcuts to set several properties of a graphical object with a single operation; this is among the functions in the yet-to-be completed TeXmacs Scheme graphics code. Scheme is promising for implementing each of them.

As a sketch of an implementation, styles could be defined as lists of name-value pairs, maybe association lists (this might allow easier error-checking), which can be inserted into with constructs by a function which first flattens the pairs then appends the resulting list into a '(with … object) list at the position we indicated with the dots to apply all of the properties to object. Never mind that the Scheme syntax to achieve what we want is slightly different from the description I gave, it is close enough that I hope it is convincing.

About persuasion. I hope that I convinced you that the initial effort of setting up Scheme functions pays off: one constructs a powerful graphical language in which arbitrarily complex graphics are treated uniformly.