https://github.com/akkartik/mu/blob/main/500fake-screen.mu
  1 # Testable primitives for writing to screen.
  2 #
  3 # Mu mostly uses the screen for text, but it builds it out of pixel graphics
  4 # and a bitmap font. There is no support for a blinking cursor, scrolling and
  5 # so on.
  6 #
  7 # Fake screens are primarily for testing text-mode prints. However, they do
  8 # support some rudimentary pixel operations as well. Caveats:
  9 #
 10 # - Drawing pixels atop text or vice versa is not supported. Results in a fake
 11 #   screen will not mimic real screens in these situations.
 12 # - Fake screens currently also assume a fixed-width 8x16 font.
 13 # - Combining characters don't render like in a real screen (which itself
 14 #   isn't ideal).
 15 
 16 type screen {
 17   # text mode
 18   width: int
 19   height: int
 20   data: (handle array screen-cell)
 21   cursor-x: int  # [0..width)
 22   cursor-y: int  # [0..height)
 23   # pixel graphics
 24   pixels: (handle array byte)
 25 }
 26 
 27 type screen-cell {
 28   data: code-point
 29   color: int
 30   background-color: int
 31   unused?: boolean
 32 }
 33 
 34 fn initialize-screen _screen: (addr screen), width: int, height: int, pixel-graphics?: boolean {
 35   var screen/esi: (addr screen) <- copy _screen
 36   var tmp/eax: int <- copy 0
 37   var dest/edi: (addr int) <- copy 0
 38   # screen->width = width
 39   dest <- get screen, width
 40   tmp <- copy width
 41   copy-to *dest, tmp
 42   # screen->height = height
 43   dest <- get screen, height
 44   tmp <- copy height
 45   copy-to *dest, tmp
 46   # populate screen->data
 47   {
 48     var data-ah/edi: (addr handle array screen-cell) <- get screen, data
 49     var capacity/eax: int <- copy width
 50     capacity <- multiply height
 51     #
 52     populate data-ah, capacity
 53   }
 54   # if necessary, populate screen->pixels
 55   {
 56     compare pixel-graphics?, 0/false
 57     break-if-=
 58     var pixels-ah/edi: (addr handle array byte) <- get screen, pixels
 59     var capacity/eax: int <- copy width
 60     capacity <- shift-left 3/log2-font-width
 61     capacity <- multiply height
 62     capacity <- shift-left 4/log2-font-height
 63     #
 64     populate pixels-ah, capacity
 65   }
 66   # screen->cursor-x = 0
 67   dest <- get screen, cursor-x
 68   copy-to *dest, 0
 69   # screen->cursor-y = 0
 70   dest <- get screen, cursor-y
 71   copy-to *dest, 0
 72 }
 73 
 74 # in code-point-utf8s
 75 fn screen-size _screen: (addr screen) -> _/eax: int, _/ecx: int {
 76   var screen/esi: (addr screen) <- copy _screen
 77   var width/eax: int <- copy 0
 78   var height/ecx: int <- copy 0
 79   compare screen, 0
 80   {
 81     break-if-!=
 82     return 0x80/128, 0x30/48
 83   }
 84   # fake screen
 85   var tmp/edx: (addr int) <- get screen, width
 86   width <- copy *tmp
 87   tmp <- get screen, height
 88   height <- copy *tmp
 89   return width, height
 90 }
 91 
 92 # testable screen primitive
 93 # return number of 8x16 units drawn
 94 fn draw-code-point _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int -> _/eax: int {
 95   var screen/esi: (addr screen) <- copy _screen
 96   {
 97     compare screen, 0
 98     break-if-!=
 99     var result/eax: int <- draw-code-point-on-real-screen c, x, y, color, background-color
100     return result
101   }
102   # fake screen
103   var wide?/eax: boolean <- wide-code-point? c
104   compare wide?, 0/false
105   {
106     break-if-=
107     draw-wide-code-point-on-fake-screen screen, c, x, y, color, background-color
108     return 2
109   }
110   draw-narrow-code-point-on-fake-screen screen, c, x, y, color, background-color
111   return 1
112 }
113 
114 fn overlay-code-point _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int -> _/eax: int {
115   var screen/esi: (addr screen) <- copy _screen
116   {
117     compare screen, 0
118     break-if-!=
119     var result/eax: int <- overlay-code-point-on-real-screen c, x, y, color, background-color
120     return result
121   }
122   # fake screen
123   # TODO: support overlays in fake screen
124   var wide?/eax: boolean <- wide-code-point? c
125   compare wide?, 0/false
126   {
127     break-if-=
128     draw-wide-code-point-on-fake-screen screen, c, x, y, color, background-color
129     return 2
130   }
131   draw-narrow-code-point-on-fake-screen screen, c, x, y, color, background-color
132   return 1
133 }
134 
135 fn draw-narrow-code-point-on-fake-screen _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int {
136   var screen/esi: (addr screen) <- copy _screen
137   # ignore if out of bounds
138   {
139     compare x, 0
140     break-if->=
141     return
142   }
143   {
144     var xmax-addr/eax: (addr int) <- get screen, width
145     var xmax/eax: int <- copy *xmax-addr
146     compare x, xmax
147     break-if-<
148     {
149       loop
150     }
151     return
152   }
153   {
154     compare y, 0
155     break-if->=
156     return
157   }
158   {
159     var ymax-addr/eax: (addr int) <- get screen, height
160     var ymax/eax: int <- copy *ymax-addr
161     compare y, ymax
162     break-if-<
163     return
164   }
165   #
166   var index/ecx: int <- screen-cell-index screen, x, y
167   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
168   var data/eax: (addr array screen-cell) <- lookup *data-ah
169   var offset/ecx: (offset screen-cell) <- compute-offset data, index
170   var dest-cell/ecx: (addr screen-cell) <- index data, offset
171   var dest-code-point/eax: (addr code-point) <- get dest-cell, data
172   var c2/edx: code-point <- copy c
173   copy-to *dest-code-point, c2
174   var dest-color/eax: (addr int) <- get dest-cell, color
175   var src-color/edx: int <- copy color
176   copy-to *dest-color, src-color
177   dest-color <- get dest-cell, background-color
178   src-color <- copy background-color
179   copy-to *dest-color, src-color
180   var dest/eax: (addr boolean) <- get dest-cell, unused?
181   copy-to *dest, 0/false
182 }
183 
184 fn draw-wide-code-point-on-fake-screen _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int {
185   var screen/esi: (addr screen) <- copy _screen
186   # ignore if out of bounds
187   {
188     compare x, 0
189     break-if->=
190     return
191   }
192   {
193     var xmax-addr/eax: (addr int) <- get screen, width
194     var xmax/eax: int <- copy *xmax-addr
195     xmax <- decrement  # wide code-points need an extra unit
196     compare x, xmax
197     break-if-<
198     return
199   }
200   {
201     compare y, 0
202     break-if->=
203     return
204   }
205   {
206     var ymax-addr/eax: (addr int) <- get screen, height
207     var ymax/eax: int <- copy *ymax-addr
208     compare y, ymax
209     break-if-<
210     return
211   }
212   #
213   var index/ecx: int <- screen-cell-index screen, x, y
214   {
215     var data-ah/eax: (addr handle array screen-cell) <- get screen, data
216     var data/eax: (addr array screen-cell) <- lookup *data-ah
217     var offset/ecx: (offset screen-cell) <- compute-offset data, index
218     var dest-cell/ecx: (addr screen-cell) <- index data, offset
219     var dest-code-point/eax: (addr code-point) <- get dest-cell, data
220     var c2/edx: code-point <- copy c
221     copy-to *dest-code-point, c2
222     var dest-color/eax: (addr int) <- get dest-cell, color
223     var src-color/edx: int <- copy color
224     copy-to *dest-color, src-color
225     dest-color <- get dest-cell, background-color
226     src-color <- copy background-color
227     copy-to *dest-color, src-color
228     var dest/eax: (addr boolean) <- get dest-cell, unused?
229     copy-to *dest, 0/false
230   }
231   # set next screen-cell to unused
232   index <- increment
233   {
234     var data-ah/eax: (addr handle array screen-cell) <- get screen, data
235     var data/eax: (addr array screen-cell) <- lookup *data-ah
236     var offset/ecx: (offset screen-cell) <- compute-offset data, index
237     var dest-cell/ecx: (addr screen-cell) <- index data, offset
238     var dest/eax: (addr boolean) <- get dest-cell, unused?
239     copy-to *dest, 1/true
240   }
241 }
242 
243 # fake screens only
244 fn screen-cell-index _screen: (addr screen), x: int, y: int -> _/ecx: int {
245   var screen/esi: (addr screen) <- copy _screen
246   var width-addr/eax: (addr int) <- get screen, width
247   var result/ecx: int <- copy y
248   result <- multiply *width-addr
249   result <- add x
250   return result
251 }
252 
253 fn cursor-position _screen: (addr screen) -> _/eax: int, _/ecx: int {
254   var screen/esi: (addr screen) <- copy _screen
255   {
256     compare screen, 0
257     break-if-!=
258     var x/eax: int <- copy 0
259     var y/ecx: int <- copy 0
260     x, y <- cursor-position-on-real-screen
261     return x, y
262   }
263   # fake screen
264   var cursor-x-addr/eax: (addr int) <- get screen, cursor-x
265   var cursor-y-addr/ecx: (addr int) <- get screen, cursor-y
266   return *cursor-x-addr, *cursor-y-addr
267 }
268 
269 fn set-cursor-position _screen: (addr screen), x: int, y: int {
270   var screen/esi: (addr screen) <- copy _screen
271   {
272     compare screen, 0
273     break-if-!=
274     set-cursor-position-on-real-screen x, y
275     return
276   }
277   # fake screen
278   # ignore x < 0
279   {
280     compare x, 0
281     break-if->=
282     return
283   }
284   # ignore x >= width
285   {
286     var width-addr/eax: (addr int) <- get screen, width
287     var width/eax: int <- copy *width-addr
288     compare x, width
289     break-if-<=
290     return
291   }
292   # ignore y < 0
293   {
294     compare y, 0
295     break-if->=
296     return
297   }
298   # ignore y >= height
299   {
300     var height-addr/eax: (addr int) <- get screen, height
301     var height/eax: int <- copy *height-addr
302     compare y, height
303     break-if-<
304     return
305   }
306   # screen->cursor-x = x
307   var dest/edi: (addr int) <- get screen, cursor-x
308   var src/eax: int <- copy x
309   copy-to *dest, src
310   # screen->cursor-y = y
311   dest <- get screen, cursor-y
312   src <- copy y
313   copy-to *dest, src
314 }
315 
316 fn draw-cursor screen: (addr screen), c: code-point {
317   {
318     compare screen, 0
319     break-if-!=
320     draw-cursor-on-real-screen c
321     return
322   }
323   # fake screen
324   var cursor-x/eax: int <- copy 0
325   var cursor-y/ecx: int <- copy 0
326   cursor-x, cursor-y <- cursor-position screen
327   var dummy/eax: int <- draw-code-point screen, c, cursor-x, cursor-y, 0/fg, 7/bg
328 }
329 
330 fn clear-screen _screen: (addr screen) {
331   var screen/esi: (addr screen) <- copy _screen
332   {
333     compare screen, 0
334     break-if-!=
335     clear-real-screen
336     return
337   }
338   # fake screen
339   set-cursor-position screen, 0, 0
340   var y/eax: int <- copy 0
341   var height/ecx: (addr int) <- get screen, height
342   {
343     compare y, *height
344     break-if->=
345     var x/edx: int <- copy 0
346     var width/ebx: (addr int) <- get screen, width
347     {
348       compare x, *width
349       break-if->=
350       var dummy/eax: int <- draw-code-point screen, 0/nul, x, y, 0/fg=black, 0/bg=black
351       x <- increment
352       loop
353     }
354     y <- increment
355     loop
356   }
357   set-cursor-position screen, 0, 0
358   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
359   var pixels/eax: (addr array byte) <- lookup *pixels-ah
360   var i/ecx: int <- copy 0
361   var max/edx: int <- length pixels
362   {
363     compare i, max
364     break-if->=
365     var curr/eax: (addr byte) <- index pixels, i
366     var zero/ebx: byte <- copy 0
367     copy-byte-to *curr, zero
368     i <- increment
369     loop
370   }
371 }
372 
373 fn fake-screen-empty? _screen: (addr screen) -> _/eax: boolean {
374   var screen/esi: (addr screen) <- copy _screen
375   var y/eax: int <- copy 0
376   var height/ecx: (addr int) <- get screen, height
377   {
378     compare y, *height
379     break-if->=
380     var x/edx: int <- copy 0
381     var width/ebx: (addr int) <- get screen, width
382     {
383       compare x, *width
384       break-if->=
385       var c/eax: code-point <- screen-code-point-at screen, x, y
386       {
387         compare c, 0
388         break-if-=
389         compare c, 0x20/space
390         break-if-=
391         return 0/false
392       }
393       x <- increment
394       loop
395     }
396     y <- increment
397     loop
398   }
399   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
400   var pixels/eax: (addr array byte) <- lookup *pixels-ah
401   var y/ebx: int <- copy 0
402   var height-addr/edx: (addr int) <- get screen, height
403   var height/edx: int <- copy *height-addr
404   height <- shift-left 4/log2-font-height
405   {
406     compare y, height
407     break-if->=
408     var width-addr/edx: (addr int) <- get screen, width
409     var width/edx: int <- copy *width-addr
410     width <- shift-left 3/log2-font-width
411     var x/edi: int <- copy 0
412     {
413       compare x, width
414       break-if->=
415       var index/ecx: int <- pixel-index screen, x, y
416       var color-addr/ecx: (addr byte) <- index pixels, index
417       var color/ecx: byte <- copy-byte *color-addr
418       compare color, 0
419       {
420         break-if-=
421         return 0/false
422       }
423       x <- increment
424       loop
425     }
426     y <- increment
427     loop
428   }
429   return 1/true
430 }
431 
432 fn clear-rect _screen: (addr screen), xmin: int, ymin: int, xmax: int, ymax: int, background-color: int {
433   var screen/esi: (addr screen) <- copy _screen
434   {
435     compare screen, 0
436     break-if-!=
437     clear-rect-on-real-screen xmin, ymin, xmax, ymax, background-color
438     return
439   }
440   # fake screen
441   set-cursor-position screen, 0, 0
442   var y/eax: int <- copy ymin
443   var ymax/ecx: int <- copy ymax
444   {
445     compare y, ymax
446     break-if->=
447     var x/edx: int <- copy xmin
448     var xmax/ebx: int <- copy xmax
449     {
450       compare x, xmax
451       break-if->=
452       var dummy/eax: int <- draw-code-point screen, 0x20/space, x, y, 0/fg, background-color
453       x <- increment
454       loop
455     }
456     y <- increment
457     loop
458   }
459   set-cursor-position screen, 0, 0
460 }
461 
462 # there's no code-point-utf8 that guarantees to cover every pixel, so we'll bump down
463 # to pixels for a real screen
464 fn clear-real-screen {
465   var y/eax: int <- copy 0
466   {
467     compare y, 0x300/screen-height=768
468     break-if->=
469     var x/edx: int <- copy 0
470     {
471       compare x, 0x400/screen-width=1024
472       break-if->=
473       pixel-on-real-screen x, y, 0/color=black
474       x <- increment
475       loop
476     }
477     y <- increment
478     loop
479   }
480 }
481 
482 fn clear-rect-on-real-screen xmin: int, ymin: int, xmax: int, ymax: int, background-color: int {
483   var y/eax: int <- copy ymin
484   y <- shift-left 4/log2-font-height
485   var ymax/ecx: int <- copy ymax
486   ymax <- shift-left 4/log2-font-height
487   {
488     compare y, ymax
489     break-if->=
490     var x/edx: int <- copy xmin
491     x <- shift-left 3/log2-font-width
492     var xmax/ebx: int <- copy xmax
493     xmax <- shift-left 3/log2-font-width
494     {
495       compare x, xmax
496       break-if->=
497       pixel-on-real-screen x, y, background-color
498       x <- increment
499       loop
500     }
501     y <- increment
502     loop
503   }
504 }
505 
506 fn screen-cell-unused-at? _screen: (addr screen), x: int, y: int -> _/eax: boolean {
507   var screen/esi: (addr screen) <- copy _screen
508   var index/ecx: int <- screen-cell-index screen, x, y
509   var result/eax: boolean <- screen-cell-unused-at-index? screen, index
510   return result
511 }
512 
513 fn screen-cell-unused-at-index? _screen: (addr screen), _index: int -> _/eax: boolean {
514   var screen/esi: (addr screen) <- copy _screen
515   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
516   var data/eax: (addr array screen-cell) <- lookup *data-ah
517   var index/ecx: int <- copy _index
518   var offset/ecx: (offset screen-cell) <- compute-offset data, index
519   var cell/eax: (addr screen-cell) <- index data, offset
520   var src/eax: (addr boolean) <- get cell, unused?
521   return *src
522 }
523 
524 fn screen-code-point-at _screen: (addr screen), x: int, y: int -> _/eax: code-point {
525   var screen/esi: (addr screen) <- copy _screen
526   var index/ecx: int <- screen-cell-index screen, x, y
527   var result/eax: code-point <- screen-code-point-at-index screen, index
528   return result
529 }
530 
531 fn screen-code-point-at-index _screen: (addr screen), _index: int -> _/eax: code-point {
532   var screen/esi: (addr screen) <- copy _screen
533   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
534   var data/eax: (addr array screen-cell) <- lookup *data-ah
535   var index/ecx: int <- copy _index
536   var offset/ecx: (offset screen-cell) <- compute-offset data, index
537   var cell/eax: (addr screen-cell) <- index data, offset
538   var src/eax: (addr code-point) <- get cell, data
539   return *src
540 }
541 
542 fn screen-color-at _screen: (addr screen), x: int, y: int -> _/eax: int {
543   var screen/esi: (addr screen) <- copy _screen
544   var index/ecx: int <- screen-cell-index screen, x, y
545   var result/eax: int <- screen-color-at-index screen, index
546   return result
547 }
548 
549 fn screen-color-at-index _screen: (addr screen), _index: int -> _/eax: int {
550   var screen/esi: (addr screen) <- copy _screen
551   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
552   var data/eax: (addr array screen-cell) <- lookup *data-ah
553   var index/ecx: int <- copy _index
554   var offset/ecx: (offset screen-cell) <- compute-offset data, index
555   var cell/eax: (addr screen-cell) <- index data, offset
556   var src/eax: (addr int) <- get cell, color
557   var result/eax: int <- copy *src
558   return result
559 }
560 
561 fn screen-background-color-at _screen: (addr screen), x: int, y: int -> _/eax: int {
562   var screen/esi: (addr screen) <- copy _screen
563   var index/ecx: int <- screen-cell-index screen, x, y
564   var result/eax: int <- screen-background-color-at-index screen, index
565   return result
566 }
567 
568 fn screen-background-color-at-index _screen: (addr screen), _index: int -> _/eax: int {
569   var screen/esi: (addr screen) <- copy _screen
570   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
571   var data/eax: (addr array screen-cell) <- lookup *data-ah
572   var index/ecx: int <- copy _index
573   var offset/ecx: (offset screen-cell) <- compute-offset data, index
574   var cell/eax: (addr screen-cell) <- index data, offset
575   var src/eax: (addr int) <- get cell, background-color
576   var result/eax: int <- copy *src
577   return result
578 }
579 
580 fn pixel screen: (addr screen), x: int, y: int, color: int {
581   {
582     compare screen, 0
583     break-if-!=
584     pixel-on-real-screen x, y, color
585     return
586   }
587   # fake screen
588   var screen/esi: (addr screen) <- copy screen
589   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
590   var pixels/eax: (addr array byte) <- lookup *pixels-ah
591   {
592     compare pixels, 0
593     break-if-!=
594     abort "pixel graphics not enabled for this screen"
595   }
596   # ignore if out of bounds
597   {
598     compare x, 0
599     break-if->=
600     return
601   }
602   {
603     var xmax-addr/eax: (addr int) <- get screen, width
604     var xmax/eax: int <- copy *xmax-addr
605     xmax <- shift-left 3/log2-font-width
606     compare x, xmax
607     break-if-<
608     return
609   }
610   {
611     compare y, 0
612     break-if->=
613     return
614   }
615   {
616     var ymax-addr/eax: (addr int) <- get screen, height
617     var ymax/eax: int <- copy *ymax-addr
618     ymax <- shift-left 4/log2-font-height
619     compare y, ymax
620     break-if-<
621     return
622   }
623   #
624   var index/ecx: int <- pixel-index screen, x, y
625   var dest/ecx: (addr byte) <- index pixels, index
626   var src/eax: byte <- copy-byte color
627   copy-byte-to *dest, src
628 }
629 
630 fn pixel-index _screen: (addr screen), x: int, y: int -> _/ecx: int {
631   var screen/esi: (addr screen) <- copy _screen
632   var width-addr/eax: (addr int) <- get screen, width
633   var result/ecx: int <- copy y
634   result <- multiply *width-addr
635   result <- shift-left 3/log2-font-width
636   result <- add x
637   return result
638 }
639 
640 # double-buffering primitive
641 # 'screen' must be a fake screen. 'target-screen' is usually real.
642 # Both screens must have the same size.
643 fn copy-pixels _screen: (addr screen), target-screen: (addr screen) {
644   var screen/esi: (addr screen) <- copy _screen
645   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
646   var _pixels/eax: (addr array byte) <- lookup *pixels-ah
647   var pixels/edi: (addr array byte) <- copy _pixels
648   var width-a/edx: (addr int) <- get screen, width
649   var width/edx: int <- copy *width-a
650   width <- shift-left 3/log2-font-width
651   var height-a/ebx: (addr int) <- get screen, height
652   var height/ebx: int <- copy *height-a
653   height <- shift-left 4/log2-font-height
654   var i/esi: int <- copy 0
655   var y/ecx: int <- copy 0
656   {
657     # screen top left pixels x y width height
658     compare y, height
659     break-if->=
660     var x/eax: int <- copy 0
661     {
662       compare x, width
663       break-if->=
664       {
665         var color-addr/ebx: (addr byte) <- index pixels, i
666         var color/ebx: byte <- copy-byte *color-addr
667         var color2/ebx: int <- copy color
668         pixel target-screen, x, y, color2
669       }
670       x <- increment
671       i <- increment
672       loop
673     }
674     y <- increment
675     loop
676   }
677 }
678 
679 # It turns out double-buffering screen-cells is useless because rendering fonts
680 # takes too long. (At least under Qemu.)
681 # So we'll instead convert screen-cells to pixels when double-buffering.
682 # 'screen' must be a fake screen.
683 fn convert-screen-cells-to-pixels _screen: (addr screen) {
684   var screen/esi: (addr screen) <- copy _screen
685   var width-a/ebx: (addr int) <- get screen, width
686   var height-a/edx: (addr int) <- get screen, height
687   var data-ah/eax: (addr handle array byte) <- get screen, pixels
688   var _data/eax: (addr array byte) <- lookup *data-ah
689   var data: (addr array byte)
690   copy-to data, _data
691   var y/ecx: int <- copy 0
692   {
693     compare y, *height-a
694     break-if->=
695     var x/edi: int <- copy 0
696     $convert-screen-cells-to-pixels:loop-x: {
697       compare x, *width-a
698       break-if->=
699       {
700         var tmp/eax: code-point <- screen-code-point-at screen, x, y
701         # skip null code-points that only get created when clearing screen
702         # there may be other pixels drawn there, and we don't want to clobber them
703         # this is a situation where fake screens aren't faithful to real screens; we don't support overlap between screen-cells and raw pixels
704         compare tmp, 0
705         break-if-=
706         var c: code-point
707         copy-to c, tmp
708         var tmp/eax: int <- screen-color-at screen, x, y
709         var fg: int
710         copy-to fg, tmp
711         var bg/eax: int <- screen-background-color-at screen, x, y
712         var offset/eax: int <- draw-code-point-on-screen-array data, c, x, y, fg, bg, *width-a, *height-a
713         x <- add offset
714         loop $convert-screen-cells-to-pixels:loop-x
715       }
716       x <- increment
717       loop
718     }
719     y <- increment
720     loop
721   }
722 }