Cistercian numerals

From Rosetta Code
Task
Cistercian numerals
You are encouraged to solve this task according to the task description, using any language you may know.

Cistercian numerals were used across Europe by Cistercian monks during the Late Medieval Period as an alternative to Roman numerals. They were used to represent base 10 integers from 0 to 9999.

How they work

All Cistercian numerals begin with a vertical line segment, which by itself represents the number 0. Then, glyphs representing the digits 1 through 9 are optionally added to the four quadrants surrounding the vertical line segment. These glyphs are drawn with vertical and horizontal symmetry about the initial line segment. Each quadrant corresponds to a digit place in the number:

  • The upper-right quadrant represents the ones place.
  • The upper-left quadrant represents the tens place.
  • The lower-right quadrant represents the hundreds place.
  • The lower-left quadrant represents the thousands place.

Please consult the following image for examples of Cistercian numerals showing each glyph: [1]

Task
  • Write a function/procedure/routine to display any given Cistercian numeral. This could be done by drawing to the display, creating an image, or even as text (as long as it is a reasonable facsimile).
  • Use the routine to show the following Cistercian numerals:
  • 0
  • 1
  • 20
  • 300
  • 4000
  • 5555
  • 6789
  • And a number of your choice!
Notes

Due to the inability to upload images to Rosetta Code as of this task's creation, showing output here on this page is not required. However, it is welcomed — especially for text output.

See also

68000 Assembly

This Sega Genesis cartridge can be compiled with VASM and run in the Fusion emulator.

;CONSTANTS
VFLIP equ %0001000000000000
HFLIP equ %0000100000000000
;Ram Variables
Cursor_X equ $00FF0000
Cursor_Y equ Cursor_X+1
temp_cursor_x equ $00FF0002
temp_cursor_y equ $00FF0003
;Video Ports
VDP_data	EQU	$C00000	; VDP data, R/W word or longword access only
VDP_ctrl	EQU	$C00004	; VDP control, word or longword writes only
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 					Traps
	DC.L	$FFFFFE00		;SP register value
	DC.L	ProgramStart	        ;Start of Program Code
	DS.L	7,IntReturn		; bus err,addr err,illegal inst,divzero,CHK,TRAPV,priv viol
	DC.L	IntReturn		; TRACE
	DC.L	IntReturn		; Line A (1010) emulator
	DC.L	IntReturn		; Line F (1111) emulator
	DS.L	4,IntReturn		; Reserverd /Coprocessor/Format err/ Uninit Interrupt
	DS.L	8,IntReturn		; Reserved
	DC.L	IntReturn		; spurious interrupt
	DC.L	IntReturn		; IRQ level 1
	DC.L	IntReturn		; IRQ level 2 EXT
	DC.L	IntReturn		; IRQ level 3
	DC.L	IntReturn		; IRQ level 4 Hsync
	DC.L	IntReturn		; IRQ level 5
	DC.L	IntReturn		; IRQ level 6 Vsync
	DC.L	IntReturn		; IRQ level 7 
	DS.L	16,IntReturn	; TRAPs
	DS.L	16,IntReturn	; Misc (FP/MMU)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;					Header
	DC.B	"SEGA GENESIS    "	;System Name
	DC.B	"(C)CHBI "			;Copyright
 	DC.B	"2019.JAN"			;Date
	DC.B	"ChibiAkumas.com                                 " ; Cart Name
	DC.B	"ChibiAkumas.com                                 " ; Cart Name (Alt)
	DC.B	"GM CHIBI001-00"	;TT NNNNNNNN-RR T=Type (GM=Game) N=game Num  R=Revision
	DC.W	$0000				;16-bit Checksum (Address $000200+)
	DC.B	"J               "	;Control Data (J=3button K=Keyboard 6=6button C=cdrom)
	DC.L	$00000000			;ROM Start
	DC.L	$003FFFFF			;ROM Length
	DC.L	$00FF0000,$00FFFFFF	;RAM start/end (fixed)
	DC.B	"            "		;External RAM Data
	DC.B	"            "		;Modem Data
	DC.B	"                                        " ;MEMO
	DC.B	"JUE             "	;Regions Allowed

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;					Generic Interrupt Handler
IntReturn:
	rte
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;					Program Start
ProgramStart:
	;initialize TMSS (TradeMark Security System)
	move.b ($A10001),D0		;A10001 test the hardware version
	and.b #$0F,D0
	beq	NoTmss				;branch if no TMSS chip
	move.l #'SEGA',($A14000);A14000 disable TMSS 
NoTmss:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;					Set Up Screen Settings

	lea VDPSettings,A5		;Initialize Screen Registers
	move.l #VDPSettingsEnd-VDPSettings,D1 ;length of Settings
	
	move.w (VDP_ctrl),D0	;C00004 read VDP status (interrupt acknowledge?)
	move.l #$00008000,d5	;VDP Reg command (%8rvv)
	
NextInitByte:
	move.b (A5)+,D5			;get next video control byte
	move.w D5,(VDP_ctrl)	;C00004 send write register command to VDP
		;   8RVV - R=Reg V=Value
	add.w #$0100,D5			;point to next VDP register
	dbra D1,NextInitByte	;loop for rest of block
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;					Set up palette and graphics
	
	move.l #$C0000000,d0	;Color 0
	move.l d0,VDP_Ctrl
	MOVE.W #$0A00,VDP_Data		;BLUE
	
	move.l #$C01E0000,d0	;Color 0
	move.l d0,VDP_Ctrl
	MOVE.W #$00EE,VDP_Data		;YELLOW
	
	lea Graphics,a0						;background tiles
	move.w #(GraphicsEnd-Graphics)-1,d1	;data size
	MOVEQ #0,D2							;start loading at tile 0 of VRAM
	jsr DefineTiles
	
	;Turn on screen
	move.w	#$8144,(VDP_Ctrl);C00004 reg 1 = 0x44 unblank display

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;	
Main:
	CLR.B Cursor_X
	CLR.B Cursor_Y
	
	LEA TestData,a3
	jsr PrintCistercian
	jsr PrintCistercian
	jsr PrintCistercian
	jsr PrintCistercian
	jsr PrintCistercian
	jsr PrintCistercian
	jsr PrintCistercian
	jsr PrintCistercian
	jmp *					;halt the cpu - we're done
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PrintCistercian:
	;input:A3 = address of test data.
	MOVE.B Cursor_X,temp_Cursor_X
	MOVE.B Cursor_Y,temp_Cursor_Y
	MOVE.L (A3)+,D1
	;thousands, hundreds, tens, ones
	
	;PRINT TENS PLACE
	MOVE.L D1,D0
	ROR.W #8,D0		;get tens place into low byte
	AND.W #$FF,D0
	OR.W #HFLIP,D0
	jsr doPrint
	
	addq.b #1,(Cursor_X)	;INC Xpos
	
	;PRINT ONES PLACE
	MOVE.L D1,D0
	AND.W #$FF,D0
	JSR doPrint
	
	
	MOVE.B temp_Cursor_X,Cursor_X
	ADDQ.B #1,cursor_Y
	
	;PRINT STICK CENTER
	MOVE.W #10,D0	;the center of the stick
	OR.W #HFLIP,D0
	jsr doPrint
	addq.b #1,(Cursor_X)	;INC Xpos
	MOVE.W #10,D0	;the center of the stick
	jsr doPrint
	
	MOVE.B temp_Cursor_X,Cursor_X
	ADDQ.B #1,cursor_Y
	
	;PRINT THOUSANDS PLACE
	MOVE.L D1,D0
	SWAP D0
	ROR.W #8,D0		;get thousands place into low byte
	AND.W #$FF,D0
	OR.W #(HFLIP|VFLIP),D0
	jsr doPrint
	
	addq.b #1,(Cursor_X)	;INC Xpos
	MOVE.L D1,D0
	SWAP D0
	AND.W #$FF,D0
	OR.W #(VFLIP),D0
	jsr doPrint	
	
	MOVE.B temp_Cursor_X,Cursor_X
	MOVE.B temp_Cursor_Y,Cursor_Y
	ADDQ.B #3,Cursor_X
	
	rts
doPrint:
;;; this code outputs the tile index in D0 to the Genesis's tilemap... don't worry if it doesn't make sense!
	Move.L  #$40000003,d5	
	clr.l d4			

	Move.B (Cursor_Y),D4	
	rol.L #8,D4				
	rol.L #8,D4
	rol.L #7,D4				
	add.L D4,D5				
	
	Move.B (Cursor_X),D4
	rol.L #8,D4				
	rol.L #8,D4
	rol.L #1,D4				
	add.L D4,D5				
	
	MOVE.L D5,(VDP_ctrl)	
	MOVE.W D0,(VDP_data)
	
	rts
TestData:
;I used 10 for zero since otherwise we'd have a bunch of sticks as the blank tile... not good!
	DC.B 10,10,10,10
	DC.B 10,10,10,1
	DC.B 10,10,2,10
	DC.B 10,3,10,10
	DC.B 4,10,10,10
	DC.B 5,5,5,5
	DC.B 6,7,8,9
	DC.B 1,2,3,4
	
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	
DefineTiles:						;Copy D1 bytes of data from A0 to VDP memory D2 
	jsr prepareVram					;Calculate the memory location we want to write
.again:								; the tile pattern definitions to
		move.l (a0)+,(VDP_data)				
		dbra d1,.again
	rts
	
	
			
prepareVram:		
	;input: D2 = the vram memory address you want to write to. 
										;To select a memory location D2 we need to calculate 
										;the command byte... depending on the memory location
	MOVEM.L D0-D7/A0-A6,-(SP)							;$7FFF0003 = Vram $FFFF.... $40000000=Vram $0000
		move.l d2,d0
		and.w #%1100000000000000,d0		;Shift the top two bits to the far right 
		rol.w #2,d0
		
		and.l #%0011111111111111,d2	    ; shift all the other bits left two bytes
		rol.l #8,d2		
		rol.l #8,d2
		
		or.l d0,d2						
		or.l #$40000000,d2				;Set the second bit from the top to 1
										;#%01000000 00000000 00000000 00000000
		move.l d2,(VDP_ctrl)
	MOVEM.L (SP)+,D0-D7/A0-A6
	rts
Graphics:
	;cistercian numerals
	DC.L 0,0,0,0,0,0,0,0	;padding - this determines the default background tile.
	dc.l $FFFFFFFF,$F0000000,$F0000000,$F0000000,$F0000000,$F0000000,$F0000000,$F0000000		;1
	dc.l $F0000000,$F0000000,$F0000000,$F0000000,$FFFFFFFF,$F0000000,$F0000000,$F0000000		;2
	dc.l $FF000000,$F0F00000,$F00F0000,$F000F000,$F0000F00,$F00000F0,$F000000F,$F0000000		;3
	dc.l $F0000000,$F000000F,$F00000F0,$F0000F00,$F000F000,$F00F0000,$F0F00000,$FF000000		;4
	dc.l $FFFFFFFF,$F00000F0,$F0000F00,$F000F000,$F00F0000,$F0F00000,$FF000000,$F0000000		;5
	dc.l $F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F		;6
	dc.l $FFFFFFFF,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F		;7
	dc.l $F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$FFFFFFFF		;8
	dc.l $FFFFFFFF,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$F000000F,$FFFFFFFF		;9
	DC.L $F0000000,$F0000000,$F0000000,$F0000000,$F0000000,$F0000000,$F0000000,$F0000000		;the "stick"
GraphicsEnd:
VDPSettings:
	DC.B $04 ; 0 mode register 1											---H-1M-
	DC.B $04 ; 1 mode register 2											-DVdP---
	DC.B $30 ; 2 name table base for scroll A (A=top 3 bits)				--AAA--- = $C000
	DC.B $3C ; 3 name table base for window (A=top 4 bits / 5 in H40 Mode)	--AAAAA- = $F000
	DC.B $07 ; 4 name table base for scroll B (A=top 3 bits)				-----AAA = $E000
	DC.B $6C ; 5 sprite attribute table base (A=top 7 bits / 6 in H40)		-AAAAAAA = $D800
	DC.B $00 ; 6 unused register											--------
	DC.B $00 ; 7 background color (P=Palette C=Color)						--PPCCCC
	DC.B $00 ; 8 unused register											--------
	DC.B $00 ; 9 unused register											--------
	DC.B $FF ;10 H interrupt register (L=Number of lines)					LLLLLLLL
	DC.B $00 ;11 mode register 3											----IVHL
	DC.B $81 ;12 mode register 4 (C bits both1 = H40 Cell)					C---SIIC
	DC.B $37 ;13 H scroll table base (A=Top 6 bits)							--AAAAAA = $FC00
	DC.B $00 ;14 unused register											--------
	DC.B $02 ;15 auto increment (After each Read/Write)						NNNNNNNN
	DC.B $01 ;16 scroll size (Horiz & Vert size of ScrollA & B)				--VV--HH = 64x32 tiles
	DC.B $00 ;17 window H position (D=Direction C=Cells)					D--CCCCC
	DC.B $00 ;18 window V position (D=Direction C=Cells)					D--CCCCC
	DC.B $FF ;19 DMA length count low										LLLLLLLL
	DC.B $FF ;20 DMA length count high										HHHHHHHH
	DC.B $00 ;21 DMA source address low										LLLLLLLL
	DC.B $00 ;22 DMA source address mid										MMMMMMMM
	DC.B $80 ;23 DMA source address high (C=CMD)							CCHHHHHH
VDPSettingsEnd:
	even
Output:

Screenshot of emulator

Action!

BYTE FUNC AtasciiToInternal(CHAR c)
  BYTE c2

  c2=c&$7F
  IF c2<32 THEN RETURN (c+64)
  ELSEIF c2<96 THEN RETURN (c-32) FI
RETURN (c)

PROC CharOut(CARD x BYTE y CHAR c)
  BYTE i,j,v
  CARD addr

  addr=$E000+AtasciiToInternal(c)*8
  FOR j=0 TO 7
  DO
    v=Peek(addr) i=8
    WHILE i>0
    DO
      IF (v&1)=0 THEN Color=0
      ELSE Color=1 FI
      Plot(x+i,y+j)
      v=v RSH 1 i==-1
    OD
    addr==+1
  OD
RETURN

PROC TextOut(CARD x BYTE y CHAR ARRAY text)
  BYTE i

  FOR i=1 TO text(0)
  DO
    CharOut(x,y,text(i))
    x==+8
  OD
RETURN

PROC DrawDigit(BYTE d INT x BYTE y INT dx,dy)
  IF d=1 THEN
    Plot(x,y) DrawTo(x+dx,y)
  ELSEIF d=2 THEN
    Plot(x,y+dy) DrawTo(x+dx,y+dy)
  ELSEIF d=3 THEN
    Plot(x,y) DrawTo(x+dx,y+dy)
  ELSEIF d=4 THEN
    Plot(x,y+dy) DrawTo(x+dx,y)
  ELSEIF d=5 THEN
    Plot(x,y) DrawTo(x+dx,y) DrawTo(x,y+dy)
  ELSEIF d=6 THEN
    Plot(x+dx,y) DrawTo(x+dx,y+dy)
  ELSEIF d=7 THEN
    Plot(x,y) DrawTo(x+dx,y) DrawTo(x+dx,y+dy)
  ELSEIF d=8 THEN
    Plot(x,y+dy) DrawTo(x+dx,y+dy) DrawTo(x+dx,y)
  ELSEIF d=9 THEN
    Plot(x,y) DrawTo(x+dx,y)
    DrawTo(x+dx,y+dy) DrawTo(x,y+dy)
  FI
RETURN

PROC Cystersian(CARD n INT x BYTE y,s)
  INT ms

  ms=-s
  Color=1
  Plot(x+s,y) DrawTo(x+s,y+3*s)

  DrawDigit(n MOD 10,x+s,y,s,s)
  n==/10
  DrawDigit(n MOD 10,x+s,y,ms,s)
  n==/10
  DrawDigit(n MOD 10,x+s,y+3*s,s,ms)
  n==/10
  DrawDigit(n MOD 10,x+s,y+3*s,ms,ms)
RETURN

PROC Test(CARD n INT x BYTE y,s)
  CHAR ARRAY text(5)

  StrC(n,text)
  TextOut(x+(2*s-text(0)*8)/2,y-10,text)
  Cystersian(n,x,y,s)
RETURN

PROC Main()
  CARD ARRAY numbers=[0 1 20 300 4000 5555 6789 6502 1977 2021]
  BYTE CH=$02FC,COLOR1=$02C5,COLOR2=$02C6
  BYTE s=[16],i
  INT x,y

  Graphics(8+16)
  COLOR1=$0C
  COLOR2=$02

  x=s y=2*s
  FOR i=0 TO 9
  DO
    Test(numbers(i),x,y,s)
    x==+4*s
    IF x>=320-s THEN
      x=s y==+5*s
    FI
  OD

  DO UNTIL CH#$FF OD
  CH=$FF
RETURN
Output:

Screenshot from Atari 8-bit computer

ALGOL 68

BEGIN # draw some Cistercian Numerals                                        #

    INT ch = 6;       # height of the representation of a Cistercian Numeral #
    INT cw = 5;        # width of the representation of a Cistercian Numeral #
    INT cm = ( cw + 1 ) OVER 2; # mid-point of a line in the representation  #
                                                   # of a Cistercian Numeral #
    # returns a 5x6 CHAR array representing the Cistercian Nuneral of n      #
    #         0 <= m <= 9999 must be TRUE                                    #
    OP   TOCISTERCIAN = ( INT n )[,]CHAR:
         IF n < 0 OR n > 9999 THEN                               # invalid n #
            ( "?????", "?????", "?????", "?????", "?????", "?????" )
         ELSE                                                      # n is OK #
            # if ch isn't 6 or cw isn't 5, the strinngs above and below will #
            [ 1 : ch, 1 : cw ]CHAR cn :=               # need to be adjusted #
                ( "     ", "  |  ", "  |  ", "  |  ", "  |  ", "  |  " );
            []STRING t digits = ( #1# "__",      #2# ";;__",    #3# "; /;/"
                                , #4# ";\; \",   #5# "__; /;/", #6# "; |; |"
                                , #7# "_; |; |", #8# "; |;_|",  #9# "_; |;_|"
                                );
            []STRING b digits = ( #1# "__",      #2# ";;__",    #3# "\; \"
                                , #4# " /;/",    #5# "_/;/",    #6# " |; |"
                                , #7# "_|; |",   #8# " |; |;_", #9# "_|; |;_"
                                );
            # adds 1 digit to the numeral                                     #
            PROC add digit = ( INT digit, BOOL flip horizontal, flip vertical )VOID:
                 IF digit > 0 THEN                     # have a visible digit #
                    STRING d   = IF flip vertical THEN b digits[ digit ] ELSE t digits[ digit ] FI;
                    INT x     := IF flip horizontal THEN -1 ELSE 1 FI + cm;
                    INT y     := IF flip vertical   THEN ch ELSE 1 FI;
                    INT x init = x;
                    INT x step = IF flip horizontal THEN -1 ELSE 1 FI;
                    INT y step = IF flip vertical   THEN -1 ELSE 1 FI;
                    FOR c pos FROM LWB d TO UPB d DO
                        CHAR c = d[ c pos ];
                        IF c = ";" THEN
                            y +:= y step;
                            x  := x init
                        ELSE
                            cn[ y, x ] := IF ( flip horizontal XOR flip vertical ) THEN
                                              IF c = "/" THEN "\" ELIF c = "\" THEN "/" ELSE c FI
                                          ELSE c
                                          FI;
                            x +:= x step
                        FI
                    OD
                 FI # add digit # ;
            INT v := n;
            add digit( v MOD 10, FALSE, FALSE ); v OVERAB 10;
            add digit( v MOD 10, TRUE,  FALSE ); v OVERAB 10; 
            add digit( v MOD 10, FALSE, TRUE  ); v OVERAB 10;
            add digit( v MOD 10, TRUE,  TRUE  );
            cn
         FI # TOCISTERCIAN # ;
    # inserts a Cistercian Numeral representation of n into an set of lines  #
    PROC insert cistercian = ( [,]CHAR cn, REF[]STRING lines, INT pos )VOID:
         FOR i FROM 1 TO ch DO
             lines[ i ][ pos : ( pos + cw ) - 1 ] := STRING( cn[ i, : ] )
         OD # print cistercian # ;

    []INT tests = ( 0, 20, 300, 4000, 5555, 6789, 1968 );
    # construct an array of blank lines and insert the Cistercian Numereals  #
    [ 1 : ch ]STRING lines;                                     # into them  #
    FOR i FROM LWB lines TO UPB lines DO
        lines[ i ] := " " * ( ( ( UPB tests -LWB tests ) + 1 ) * ( cw * 2 ) )
    OD;
    FOR i FROM LWB tests TO UPB tests DO print( ( whole( tests[ i ], - cw ), " " * cw ) ) OD;
    print( ( newline ) );
    INT i pos := 1 - ( cw * 2 );
    FOR i FROM LWB tests TO UPB tests DO
        insert cistercian( TOCISTERCIAN tests[ i ], lines, i pos +:= cw * 2 )
    OD;
    FOR i FROM LWB lines TO UPB lines DO print( ( lines[ i ], newline ) ) OD
    
END
Output:
    0        20       300      4000      5555      6789      1968
                                        __ __        _
  |         |         |         |       \ | /     | | |     | | |
  |       __|         |         |        \|/      |_|_|     | |_|
  |         |         |         |         |         |         |_
  |         |         | /      /|        /|\      | | |       | |
  |         |         |/      / |       /_|_\     | |_|     __|_|

AutoHotkey

CistercianNumerals(num){
    x := []    
    ;UPPER LEFT      0     1     2     3     4     5     6     7     8     9
    x[1, "UL"] := ["000","111","000","000","100","111","100","111","100","111"]
    x[2, "UL"] := ["000","000","000","001","010","010","100","100","100","100"]
    x[3, "UL"] := ["000","000","000","010","001","001","100","100","100","100"]
    x[4, "UL"] := ["000","000","111","100","000","000","100","100","111","111"]

    ;UPPER RIGHT     0     1     2     3     4     5     6     7     8     9
    x[1, "UR"] := ["000","111","000","000","001","111","001","111","001","111"]
    x[2, "UR"] := ["000","000","000","100","010","010","001","001","001","001"]
    x[3, "UR"] := ["000","000","000","010","100","100","001","001","001","001"]
    x[4, "UR"] := ["000","000","111","001","000","000","001","001","111","111"]

    ;BOTTOM LEFT     0     1     2     3     4     5     6     7     8     9
    x[1, "BL"] := ["000","000","111","100","000","000","100","100","111","111"]
    x[2, "BL"] := ["000","000","000","010","001","001","100","100","100","100"]
    x[3, "BL"] := ["000","000","000","001","010","010","100","100","100","100"]
    x[4, "BL"] := ["000","111","000","000","100","111","100","111","100","111"]

    ;BOTTOM RIGHT    0     1     2     3     4     5     6     7     8     9
    x[1, "BR"] := ["000","000","111","001","000","000","001","001","111","111"]
    x[2, "BR"] := ["000","000","000","010","100","100","001","001","001","001"]
    x[3, "BR"] := ["000","000","000","100","010","010","001","001","001","001"]
    x[4, "BR"] := ["000","111","000","000","001","111","001","111","001","111"]

    num := SubStr("0000" num, -3)
    n := StrSplit(num)    ; n.1*1000 + n.2*100 + n.3*10 + n.4
    loop 4
        res .= x[A_Index, "UL", 1+n.3] . "1" . x[A_Index, "UR", 1+n.4] . "`n"
    loop 4
        res .= "0001`n"
    loop 4
        res .= x[A_Index, "BL", 1+n.1] . "1" . x[A_Index, "BR", 1+n.2] . "`n"
    res := StrReplace(res, 0, " ")
    res := StrReplace(res, 1, "#")
    return Trim(res, "`n")
}
Examples:
Gui, font, S24, Consolas
Gui, add, Text, vE1 w150 r12
Gui, show, x0 y0
for i, num in [0, 1, 20, 300, 4000, 5555, 6789, 2022]
{
    GuiControl,, E1, % CistercianNumerals(num)
    MsgBox % num
}
return
Output:
   0   		   1    	   20   	   300   	   4000   	   5555   	   6789   	   2022

   #   		   ####		   #   		   #   		   #   		#######		#  ####		   #   
   #   		   #   		   #   		   #   		   #   		 # # # 		#  #  #		   #   
   #   		   #   		   #   		   #   		   #   		  ###  		#  #  #		   #   
   #   		   #   		####   		   #   		   #   		   #   		#######		#######
   #		   #		   #		   #		   #		   #		   #		   #
   #		   #		   #		   #		   #		   #		   #		   #
   #		   #		   #		   #		   #		   #		   #		   #
   #		   #		   #		   #		   #		   #		   #		   #
   #   		   #   		   #   		   #  #		   #   		   #   		#  #  #		####   
   #   		   #   		   #   		   # # 		  ##   		  ###  		#  #  #		   #   
   #   		   #   		   #   		   ##  		 # #   		 # # # 		#  #  #		   #   
   #   		   #   		   #   		   #   		#  #   		#######		#  ####		   #   

AWK

# syntax: GAWK -f CISTERCIAN_NUMERALS.AWK [-v debug={0|1}] [-v xc=anychar] numbers 0-9999 ...
#
# example: GAWK -f CISTERCIAN_NUMERALS.AWK 0 1 20 300 4000 5555 6789 1995 10000
#
# sorting:
#   PROCINFO["sorted_in"] is used by GAWK
#   SORTTYPE is used by Thompson Automation's TAWK
#
BEGIN {
    cistercian_init()
    for (i=1; i<=ARGC-1; i++) {
      cistercian1(ARGV[i])
    }
    exit(0)
}
function cistercian1(n,  i) {
    printf("\n%6s\n",n)
    if (!(n ~ /^[0-9]+$/ && length(n) <= 4)) {
      print("invalid")
      return
    }
    n = sprintf("%04d",n)
    cistercian2(2,1,substr(n,3,1),substr(n,4,1))
    for (i=1; i<=5; i++) { # separator between upper and lower parts
      printf("%5s%1s%5s\n","",xc,"")
    }
    cistercian2(4,3,substr(n,1,1),substr(n,2,1))
}
function cistercian2(i1,i2,n1,n2,  i,L,R) {
    for (i=1; i<=5; i++) {
      L = substr(cn_arr[i1][i],n1*6+2,5)
      R = substr(cn_arr[i2][i],n2*6+2,5)
      printf("%5s%1s%5s\n",L,xc,R)
    }
}
function cistercian_init(  header,i,j,LL,LR,UL,UR) {
# 1-9 upper-right
    cn_arr[1][++UR] = ":xxxxx:     :x    :    x:xxxxx:    x:xxxxx:    x:xxxxx:"
    cn_arr[1][++UR] = ":     :     : x   :   x :   x :    x:    x:    x:    x:"
    cn_arr[1][++UR] = ":     :     :  x  :  x  :  x  :    x:    x:    x:    x:"
    cn_arr[1][++UR] = ":     :     :   x : x   : x   :    x:    x:    x:    x:"
    cn_arr[1][++UR] = ":     :xxxxx:    x:x    :x    :    x:    x:xxxxx:xxxxx:"
# 10-90 upper-left
    cn_arr[2][++UL] = ":xxxxx:     :    x:x    :xxxxx:x    :xxxxx:x    :xxxxx:"
    cn_arr[2][++UL] = ":     :     :   x : x   : x   :x    :x    :x    :x    :"
    cn_arr[2][++UL] = ":     :     :  x  :  x  :  x  :x    :x    :x    :x    :"
    cn_arr[2][++UL] = ":     :     : x   :   x :   x :x    :x    :x    :x    :"
    cn_arr[2][++UL] = ":     :xxxxx:x    :    x:    x:x    :x    :xxxxx:xxxxx:"
# 100-900 lower-right
    cn_arr[3][++LR] = ":     :xxxxx:    x:x    :x    :    x:    x:xxxxx:xxxxx:"
    cn_arr[3][++LR] = ":     :     :   x : x   : x   :    x:    x:    x:    x:"
    cn_arr[3][++LR] = ":     :     :  x  :  x  :  x  :    x:    x:    x:    x:"
    cn_arr[3][++LR] = ":     :     : x   :   x :   x :    x:    x:    x:    x:"
    cn_arr[3][++LR] = ":xxxxx:     :x    :    x:xxxxx:    x:xxxxx:    x:xxxxx:"
# 1000-9000 lower-left
    cn_arr[4][++LL] = ":     :xxxxx:x    :    x:    x:x    :x    :xxxxx:xxxxx:"
    cn_arr[4][++LL] = ":     :     : x   :   x :   x :x    :x    :x    :x    :"
    cn_arr[4][++LL] = ":     :     :  x  :  x  :  x  :x    :x    :x    :x    :"
    cn_arr[4][++LL] = ":     :     :   x : x   : x   :x    :x    :x    :x    :"
    cn_arr[4][++LL] = ":xxxxx:     :    x:x    :xxxxx:x    :xxxxx:x    :xxxxx:"
    header =    ":00000:11111:22222:33333:44444:55555:66666:77777:88888:99999:"
    PROCINFO["sorted_in"] = "@ind_str_asc" ; SORTTYPE = 1
    sub(/^ +/,"",xc)
    xc = (xc == "") ? "x" : substr(xc,1,1) # substitution character
    for (i in cn_arr) {
      for (j in cn_arr[i]) {
        gsub(/x/,xc,cn_arr[i][j]) # change "x" to substitution character
        cn_arr[i][j] = sprintf(":%5s%s","",cn_arr[i][j]) # add zero column to table
        if (debug == 1) { printf("%s %2s %d.%d\n",cn_arr[i][j],substr("URULLRLL",i*2-1,2),i,j) }
      }
    }
    if (debug == 1) { printf("%s\n",header) }
}
Output:

     0
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

     1
     xxxxxx
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

    20
     x
     x
     x
     x
xxxxxx
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

   300
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x    x
     x   x
     x  x
     x x
     xx

  4000
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
    xx
   x x
  x  x
 x   x
x    x

  5555
xxxxxxxxxxx
 x   x   x
  x  x  x
   x x x
    xxx
     x
     x
     x
     x
     x
    xxx
   x x x
  x  x  x
 x   x   x
xxxxxxxxxxx

  6789
x    xxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x
     x
     x
     x
     x
x    x    x
x    x    x
x    x    x
x    x    x
x    xxxxxx

  1995
xxxxxxxxxxx
x    x   x
x    x  x
x    x x
xxxxxxx
     x
     x
     x
     x
     x
     xxxxxx
     x    x
     x    x
     x    x
xxxxxxxxxxx

 10000
invalid

C

Translation of: C#
#include <stdio.h>

#define GRID_SIZE 15
char canvas[GRID_SIZE][GRID_SIZE];

void initN() {
    int i, j;
    for (i = 0; i < GRID_SIZE; i++) {
        for (j = 0; j < GRID_SIZE; j++) {
            canvas[i][j] = ' ';
        }
        canvas[i][5] = 'x';
    }
}

void horizontal(size_t c1, size_t c2, size_t r) {
    size_t c;
    for (c = c1; c <= c2; c++) {
        canvas[r][c] = 'x';
    }
}

void vertical(size_t r1, size_t r2, size_t c) {
    size_t r;
    for (r = r1; r <= r2; r++) {
        canvas[r][c] = 'x';
    }
}

void diagd(size_t c1, size_t c2, size_t r) {
    size_t c;
    for (c = c1; c <= c2; c++) {
        canvas[r + c - c1][c] = 'x';
    }
}

void diagu(size_t c1, size_t c2, size_t r) {
    size_t c;
    for (c = c1; c <= c2; c++) {
        canvas[r - c + c1][c] = 'x';
    }
}

void drawOnes(int v) {
    switch (v) {
    case 1:
        horizontal(6, 10, 0);
        break;
    case 2:
        horizontal(6, 10, 4);
        break;
    case 3:
        diagd(6, 10, 0);
        break;
    case 4:
        diagu(6, 10, 4);
        break;
    case 5:
        drawOnes(1);
        drawOnes(4);
        break;
    case 6:
        vertical(0, 4, 10);
        break;
    case 7:
        drawOnes(1);
        drawOnes(6);
        break;
    case 8:
        drawOnes(2);
        drawOnes(6);
        break;
    case 9:
        drawOnes(1);
        drawOnes(8);
        break;
    default:
        break;
    }
}

void drawTens(int v) {
    switch (v) {
    case 1:
        horizontal(0, 4, 0);
        break;
    case 2:
        horizontal(0, 4, 4);
        break;
    case 3:
        diagu(0, 4, 4);
        break;
    case 4:
        diagd(0, 4, 0);
        break;
    case 5:
        drawTens(1);
        drawTens(4);
        break;
    case 6:
        vertical(0, 4, 0);
        break;
    case 7:
        drawTens(1);
        drawTens(6);
        break;
    case 8:
        drawTens(2);
        drawTens(6);
        break;
    case 9:
        drawTens(1);
        drawTens(8);
        break;
    default:
        break;
    }
}

void drawHundreds(int hundreds) {
    switch (hundreds) {
    case 1:
        horizontal(6, 10, 14);
        break;
    case 2:
        horizontal(6, 10, 10);
        break;
    case 3:
        diagu(6, 10, 14);
        break;
    case 4:
        diagd(6, 10, 10);
        break;
    case 5:
        drawHundreds(1);
        drawHundreds(4);
        break;
    case 6:
        vertical(10, 14, 10);
        break;
    case 7:
        drawHundreds(1);
        drawHundreds(6);
        break;
    case 8:
        drawHundreds(2);
        drawHundreds(6);
        break;
    case 9:
        drawHundreds(1);
        drawHundreds(8);
        break;
    default:
        break;
    }
}

void drawThousands(int thousands) {
    switch (thousands) {
    case 1:
        horizontal(0, 4, 14);
        break;
    case 2:
        horizontal(0, 4, 10);
        break;
    case 3:
        diagd(0, 4, 10);
        break;
    case 4:
        diagu(0, 4, 14);
        break;
    case 5:
        drawThousands(1);
        drawThousands(4);
        break;
    case 6:
        vertical(10, 14, 0);
        break;
    case 7:
        drawThousands(1);
        drawThousands(6);
        break;
    case 8:
        drawThousands(2);
        drawThousands(6);
        break;
    case 9:
        drawThousands(1);
        drawThousands(8);
        break;
    default:
        break;
    }
}

void draw(int v) {
    int thousands = v / 1000;
    v %= 1000;

    int hundreds = v / 100;
    v %= 100;

    int tens = v / 10;
    int ones = v % 10;

    if (thousands > 0) {
        drawThousands(thousands);
    }
    if (hundreds > 0) {
        drawHundreds(hundreds);
    }
    if (tens > 0) {
        drawTens(tens);
    }
    if (ones > 0) {
        drawOnes(ones);
    }
}

void write(FILE *out) {
    int i;
    for (i = 0; i < GRID_SIZE; i++) {
        fprintf(out, "%-.*s", GRID_SIZE, canvas[i]);
        putc('\n', out);
    }
}

void test(int n) {
    printf("%d:\n", n);
    initN();
    draw(n);
    write(stdout);
    printf("\n\n");
}

int main() {
    test(0);
    test(1);
    test(20);
    test(300);
    test(4000);
    test(5555);
    test(6789);
    test(9999);

    return 0;
}
Output:
0:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x


1:
     xxxxxx
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x


20:
     x
     x
     x
     x
xxxxxx
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x


300:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x    x
     x   x
     x  x
     x x
     xx


4000:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
    xx
   x x
  x  x
 x   x
x    x


5555:
xxxxxxxxxxx
 x   x   x
  x  x  x
   x x x
    xxx
     x
     x
     x
     x
     x
    xxx
   x x x
  x  x  x
 x   x   x
xxxxxxxxxxx


6789:
x    xxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x
     x
     x
     x
     x
x    x    x
x    x    x
x    x    x
x    x    x
x    xxxxxx


9999:
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x
     x
     x
     x
     x
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx

C++

Translation of: Go
#include <array>
#include <iostream>

template<typename T, size_t S>
using FixedSquareGrid = std::array<std::array<T, S>, S>;

struct Cistercian {
public:
    Cistercian() {
        initN();
    }

    Cistercian(int v) {
        initN();
        draw(v);
    }

    Cistercian &operator=(int v) {
        initN();
        draw(v);
    }

    friend std::ostream &operator<<(std::ostream &, const Cistercian &);

private:
    FixedSquareGrid<char, 15> canvas;

    void initN() {
        for (auto &row : canvas) {
            row.fill(' ');
            row[5] = 'x';
        }
    }

    void horizontal(size_t c1, size_t c2, size_t r) {
        for (size_t c = c1; c <= c2; c++) {
            canvas[r][c] = 'x';
        }
    }

    void vertical(size_t r1, size_t r2, size_t c) {
        for (size_t r = r1; r <= r2; r++) {
            canvas[r][c] = 'x';
        }
    }

    void diagd(size_t c1, size_t c2, size_t r) {
        for (size_t c = c1; c <= c2; c++) {
            canvas[r + c - c1][c] = 'x';
        }
    }

    void diagu(size_t c1, size_t c2, size_t r) {
        for (size_t c = c1; c <= c2; c++) {
            canvas[r - c + c1][c] = 'x';
        }
    }

    void drawOnes(int v) {
        switch (v) {
        case 1:
            horizontal(6, 10, 0);
            break;
        case 2:
            horizontal(6, 10, 4);
            break;
        case 3:
            diagd(6, 10, 0);
            break;
        case 4:
            diagu(6, 10, 4);
            break;
        case 5:
            drawOnes(1);
            drawOnes(4);
            break;
        case 6:
            vertical(0, 4, 10);
            break;
        case 7:
            drawOnes(1);
            drawOnes(6);
            break;
        case 8:
            drawOnes(2);
            drawOnes(6);
            break;
        case 9:
            drawOnes(1);
            drawOnes(8);
            break;
        default:
            break;
        }
    }

    void drawTens(int v) {
        switch (v) {
        case 1:
            horizontal(0, 4, 0);
            break;
        case 2:
            horizontal(0, 4, 4);
            break;
        case 3:
            diagu(0, 4, 4);
            break;
        case 4:
            diagd(0, 4, 0);
            break;
        case 5:
            drawTens(1);
            drawTens(4);
            break;
        case 6:
            vertical(0, 4, 0);
            break;
        case 7:
            drawTens(1);
            drawTens(6);
            break;
        case 8:
            drawTens(2);
            drawTens(6);
            break;
        case 9:
            drawTens(1);
            drawTens(8);
            break;
        default:
            break;
        }
    }

    void drawHundreds(int hundreds) {
        switch (hundreds) {
        case 1:
            horizontal(6, 10, 14);
            break;
        case 2:
            horizontal(6, 10, 10);
            break;
        case 3:
            diagu(6, 10, 14);
            break;
        case 4:
            diagd(6, 10, 10);
            break;
        case 5:
            drawHundreds(1);
            drawHundreds(4);
            break;
        case 6:
            vertical(10, 14, 10);
            break;
        case 7:
            drawHundreds(1);
            drawHundreds(6);
            break;
        case 8:
            drawHundreds(2);
            drawHundreds(6);
            break;
        case 9:
            drawHundreds(1);
            drawHundreds(8);
            break;
        default:
            break;
        }
    }

    void drawThousands(int thousands) {
        switch (thousands) {
        case 1:
            horizontal(0, 4, 14);
            break;
        case 2:
            horizontal(0, 4, 10);
            break;
        case 3:
            diagd(0, 4, 10);
            break;
        case 4:
            diagu(0, 4, 14);
            break;
        case 5:
            drawThousands(1);
            drawThousands(4);
            break;
        case 6:
            vertical(10, 14, 0);
            break;
        case 7:
            drawThousands(1);
            drawThousands(6);
            break;
        case 8:
            drawThousands(2);
            drawThousands(6);
            break;
        case 9:
            drawThousands(1);
            drawThousands(8);
            break;
        default:
            break;
        }
    }

    void draw(int v) {
        int thousands = v / 1000;
        v %= 1000;

        int hundreds = v / 100;
        v %= 100;

        int tens = v / 10;
        int ones = v % 10;

        if (thousands > 0) {
            drawThousands(thousands);
        }
        if (hundreds > 0) {
            drawHundreds(hundreds);
        }
        if (tens > 0) {
            drawTens(tens);
        }
        if (ones > 0) {
            drawOnes(ones);
        }
    }
};

std::ostream &operator<<(std::ostream &os, const Cistercian &c) {
    for (auto &row : c.canvas) {
        for (auto cell : row) {
            os << cell;
        }
        os << '\n';
    }
    return os;
}

int main() {
    for (auto number : { 0, 1, 20, 300, 4000, 5555, 6789, 9999 }) {
        std::cout << number << ":\n";

        Cistercian c(number);
        std::cout << c << '\n';
    }

    return 0;
}
Output:
0:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

1:
     xxxxxx
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

20:
     x
     x
     x
     x
xxxxxx
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

300:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x    x
     x   x
     x  x
     x x
     xx

4000:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
    xx
   x x
  x  x
 x   x
x    x

5555:
xxxxxxxxxxx
 x   x   x
  x  x  x
   x x x
    xxx
     x
     x
     x
     x
     x
    xxx
   x x x
  x  x  x
 x   x   x
xxxxxxxxxxx

6789:
x    xxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x
     x
     x
     x
     x
x    x    x
x    x    x
x    x    x
x    x    x
x    xxxxxx

9999:
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x
     x
     x
     x
     x
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx

D

Translation of: Java
import std.stdio;

class Cistercian {
    private immutable SIZE = 15;
    private char[SIZE][SIZE] canvas;

    public this(int n) {
        initN();
        draw(n);
    }

    private void initN() {
        foreach (ref row; canvas) {
            row[] = ' ';
            row[5] = 'x';
        }
    }

    private void horizontal(int c1, int c2, int r) {
        for (int c = c1; c <= c2; c++) {
            canvas[r][c] = 'x';
        }
    }

    private void vertical(int r1, int r2, int c) {
        for (int r = r1; r <= r2; r++) {
            canvas[r][c] = 'x';
        }
    }

    private void diagd(int c1, int c2, int r) {
        for (int c = c1; c <= c2; c++) {
            canvas[r + c - c1][c] = 'x';
        }
    }

    private void diagu(int c1, int c2, int r) {
        for (int c = c1; c <= c2; c++) {
            canvas[r - c + c1][c] = 'x';
        }
    }

    private void draw(int v) {
        auto thousands = v / 1000;
        v %= 1000;

        auto hundreds = v / 100;
        v %= 100;

        auto tens = v / 10;
        auto ones = v % 10;

        drawPart(1000 * thousands);
        drawPart(100 * hundreds);
        drawPart(10 * tens);
        drawPart(ones);
    }

    private void drawPart(int v) {
        switch(v) {
            case 0:
                break;

            case 1:
                horizontal(6, 10, 0);
                break;
            case 2:
                horizontal(6, 10, 4);
                break;
            case 3:
                diagd(6, 10, 0);
                break;
            case 4:
                diagu(6, 10, 4);
                break;
            case 5:
                drawPart(1);
                drawPart(4);
                break;
            case 6:
                vertical(0, 4, 10);
                break;
            case 7:
                drawPart(1);
                drawPart(6);
                break;
            case 8:
                drawPart(2);
                drawPart(6);
                break;
            case 9:
                drawPart(1);
                drawPart(8);
                break;

            case 10:
                horizontal(0, 4, 0);
                break;
            case 20:
                horizontal(0, 4, 4);
                break;
            case 30:
                diagu(0, 4, 4);
                break;
            case 40:
                diagd(0, 4, 0);
                break;
            case 50:
                drawPart(10);
                drawPart(40);
                break;
            case 60:
                vertical(0, 4, 0);
                break;
            case 70:
                drawPart(10);
                drawPart(60);
                break;
            case 80:
                drawPart(20);
                drawPart(60);
                break;
            case 90:
                drawPart(10);
                drawPart(80);
                break;
 
            case 100:
                horizontal(6, 10, 14);
                break;
            case 200:
                horizontal(6, 10, 10);
                break;
            case 300:
                diagu(6, 10, 14);
                break;
            case 400:
                diagd(6, 10, 10);
                break;
            case 500:
                drawPart(100);
                drawPart(400);
                break;
            case 600:
                vertical(10, 14, 10);
                break;
            case 700:
                drawPart(100);
                drawPart(600);
                break;
            case 800:
                drawPart(200);
                drawPart(600);
                break;
            case 900:
                drawPart(100);
                drawPart(800);
                break;
 
            case 1000:
                horizontal(0, 4, 14);
                break;
            case 2000:
                horizontal(0, 4, 10);
                break;
            case 3000:
                diagd(0, 4, 10);
                break;
            case 4000:
                diagu(0, 4, 14);
                break;
            case 5000:
                drawPart(1000);
                drawPart(4000);
                break;
            case 6000:
                vertical(10, 14, 0);
                break;
            case 7000:
                drawPart(1000);
                drawPart(6000);
                break;
            case 8000:
                drawPart(2000);
                drawPart(6000);
                break;
            case 9000:
                drawPart(1000);
                drawPart(8000);
                break;

            default:
                import std.conv;
                assert(false, "Not handled: " ~ v.to!string);
        }
    }

    public void toString(scope void delegate(const(char)[]) sink) const {
        foreach (row; canvas) {
            sink(row);
            sink("\n");
        }
    }
}

void main() {
    foreach (number; [0, 1, 20, 300, 4000, 5555, 6789, 9999]) {
        writeln(number, ':');
        auto c = new Cistercian(number);
        writeln(c);
    }
}
Output:
0:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

1:
     xxxxxx    
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

20:
     x
     x
     x
     x
xxxxxx
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

300:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x    x
     x   x
     x  x
     x x
     xx

4000:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
    xx
   x x
  x  x
 x   x
x    x

5555:
xxxxxxxxxxx
 x   x   x
  x  x  x
   x x x
    xxx
     x
     x
     x
     x
     x
    xxx
   x x x
  x  x  x
 x   x   x
xxxxxxxxxxx

6789:
x    xxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x
     x
     x
     x
     x
x    x    x
x    x    x
x    x    x
x    x    x

9999:
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x
     x
     x
     x
     x
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx

EasyLang

Run it

proc cist x y n . .
   linewidth 0.5
   dx[] = [ 4 -4 4 -4 ]
   dy[] = [ 4 4 -4 -4 ]
   for i to 4
      dx = dx[i]
      dy = dy[i]
      dy2 = 2 * dy
      d = n mod 10
      n = n div 10
      move x y
      # 
      line x y + 8
      move x y - 8
      line x y
      if d = 1
         move x y + dy2
         line x + dx y + dy2
      elif d = 2
         move x y + dy
         line x + dx y + dy
      elif d = 3
         move x y + dy2
         line x + dx y + dy
      elif d = 4
         move x y + dy
         line x + dx y + dy2
      elif d = 5
         move x y + dy
         line x + dx y + dy2
         line x y + dy2
      elif d = 6
         move x + dx y + dy
         line x + dx y + dy2
      elif d = 7
         move x y + dy2
         line x + dx y + dy2
         line x + dx y + dy
      elif d = 8
         move x y + dy
         line x + dx y + dy
         line x + dx y + dy2
      elif d = 9
         move x y + dy
         line x + dx y + dy
         line x + dx y + dy2
         line x y + dy2
      .
   .
   x += 12
.
x = 8
for n in [ 0 1 20 300 4000 5555 6789 2023 ]
   cist x 80 n
   x += 12
.

F#

// Cistercian numerals. Nigel Galloway: February 2nd., 2021
let N=[|[[|' ';' ';' '|];[|' ';' ';' '|];[|' ';' ';' '|]];
        [[|'#';'#';'#'|];[|' ';' ';' '|];[|' ';' ';' '|]];
        [[|' ';' ';' '|];[|'#';'#';'#'|];[|' ';' ';' '|]];
        [[|'#';' ';' '|];[|' ';'#';' '|];[|' ';' ';'#'|]];
        [[|' ';' ';'#'|];[|' ';'#';' '|];[|'#';' ';' '|]];
        [[|'#';'#';'#'|];[|' ';'#';' '|];[|'#';' ';' '|]];
        [[|' ';' ';'#'|];[|' ';' ';'#'|];[|' ';' ';'#'|]];
        [[|'#';'#';'#'|];[|' ';' ';'#'|];[|' ';' ';'#'|]];
        [[|' ';' ';'#'|];[|' ';' ';'#'|];[|'#';'#';'#'|]];
        [[|'#';'#';'#'|];[|' ';' ';'#'|];[|'#';'#';'#'|]];|]

let fN i g e l=N.[l]|>List.iter2(fun n g->printfn "%sO%s" ((Array.rev>>System.String)n) (System.String g)) N.[e]
               printfn "   O"
               N.[g]|>List.rev|>List.iter2(fun n g->printfn "%sO%s" ((Array.rev>>System.String)n) (System.String g)) (N.[i]|>List.rev)

[(0,0,0,0);(0,0,0,1);(0,0,2,0);(0,3,0,0);(4,0,0,0);(5,5,5,5);(6,7,8,9)]|>List.iter(fun(i,g,e,l)->printfn "\n%d%d%d%d\n____" i g e l; fN i g e l)
Output:
0000
____
   O
   O
   O
   O
   O
   O
   O

0001
____
   O###
   O
   O
   O
   O
   O
   O

0020
____
   O
###O
   O
   O
   O
   O
   O

0300
____
   O
   O
   O
   O
   O  #
   O #
   O#

4000
____
   O
   O
   O
   O
  #O
 # O
#  O

5555
____
###O###
 # O #
  #O#
   O
  #O#
 # O #
###O###

6789
____
#  O###
#  O  #
###O###
   O
#  O  #
#  O  #
#  O###

Factor

Works with: Factor version 0.99 2020-08-14
USING: combinators continuations formatting grouping io kernel
literals math.order math.text.utils multiline sequences
splitting ;

CONSTANT: numerals $[
HEREDOC: END
  +    +-+  +    +    + +  +-+  + +  +-+  + +  +-+
  |    |    |    |\   |/   |/   | |  | |  | |  | |
  |    |    +-+  | +  +    +    | +  | +  +-+  +-+
  |    |    |    |    |    |    |    |    |    |  
  |    |    |    |    |    |    |    |    |    |  
  |    |    |    |    |    |    |    |    |    |  
  +    +    +    +    +    +    +    +    +    +  
END
"\n" split harvest [ 5 group ] map flip
]

: precedence ( char char -- char )
    2dup [ CHAR: + = ] either? [ 2drop CHAR: + ] [ max ] if ;

: overwrite ( glyph glyph -- newglyph )
    [ [ precedence ] 2map ] 2map ;

: flip-slashes ( str -- new-str )
    [
        {
            { CHAR: / [ CHAR: \ ] }
            { CHAR: \ [ CHAR: / ] }
            [ ]
        } case
    ] map ;

: hflip ( seq -- newseq ) [ reverse flip-slashes ] map ;
: vflip ( seq -- newseq ) reverse [ flip-slashes ] map ;

: get-digits ( n -- seq ) 1 digit-groups 4 0 pad-tail ;

: check-cistercian ( n -- )
    0 9999 between? [ "Must be from 0 to 9999." throw ] unless ;

: .cistercian ( n -- )
    [ check-cistercian ] [ "%d:\n" printf ] [ get-digits ] tri
    [ numerals nth ] map
    [ { [ ] [ hflip ] [ vflip ] [ hflip vflip ] } spread ]
    with-datastack [ ] [ overwrite ] map-reduce [ print ] each ;

{ 0 1 20 300 4000 5555 6789 8015 } [ .cistercian nl ] each
Output:
0:
  +  
  |  
  |  
  |  
  |  
  |  
  +  

1:
  +-+
  |  
  |  
  |  
  |  
  |  
  +  

20:
  +  
  |  
+-+  
  |  
  |  
  |  
  +  

300:
  +  
  |  
  |  
  |  
  | +
  |/ 
  +  

4000:
  +  
  |  
  |  
  |  
  +  
 /|  
+ +  

5555:
+-+-+
 \|/ 
  +  
  |  
  +  
 /|\ 
+-+-+

6789:
+ +-+
| | |
+-+-+
  |  
+ | +
| | |
+ +-+

8015:
+-+-+
  |/ 
  +  
  |  
+-+  
| |  
+ +  

Fōrmulæ

Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text. Moreover, there can be multiple visual representations of the same program. Even though it is possible to have textual representation —i.e. XML, JSON— they are intended for storage and transfer purposes more than visualization and edition.

Programs in Fōrmulæ are created/edited online in its website.

In this page you can see and run the program(s) related to this task and their results. You can also change either the programs or the parameters they are called with, for experimentation, but remember that these programs were created with the main purpose of showing a clear solution of the task, and they generally lack any kind of validation.

Solution

We can take advantage of the coordinate transformations.

Part 1. Glyphs for each digit

The glyphs of each "digit" are the same, excepting they are mirrored according to its place ("units", "tens", "hundreds" and "thousands"), so we have generic code for each.

The following specification are for "units" and it is independent of size. They are referred to the top-right "quadrant" of the complete number, which has mathematical coordinate system, being (-1, -1) the bottom-left corner, and (1, 1) the opposite one, hence, the center is (0, 0).

Please notice that they are provided as an array of (9) lambda expressions. There is no glyph for zero.

Part 2. Mirroring for "tens", "hundreds" and "thousands"

The following is the specification to change the scale, according to the place and to produce the mirrored effect. Notice that there is no specification for "units", because the definitions of glyphs are based on this place and therefore there is no transformation to apply.

Part 3. Function to draw a Cistercian number

Finally, the following function creates the representation of the Cistercian number.

Notice that the origin is initially translated to the center of the graphics, and also is scaled to the size of the graphics too, in order to define the system of coordinates.

Test cases

Additional case. Creating all the Cistercian numerals in a single image

The following program creates a big image, and copies into it all the 10,000 different Cistercian numerals:

The result is a 4000 x 6010 pixels image. Click or tap on the following thumbnail to enlarge:

FutureBasic

_window = 1
begin enum 1
  _numView
  _numFld
end enum

_numHeight = 54
_lineLength = _numHeight/3


void local fn BuildWindow
  window _window, @"Cistercian Numerals",, NSWindowStyleMaskTitled + NSWindowStyleMaskClosable + NSWindowStyleMaskMiniaturizable
  subclass view _numView, (237,153,76,94)
  ViewSetFlipped( _numView, YES )
  textfield _numFld,, @"0", (237,20,76,21)
  ControlSetAlignment( _numFld, NSTextAlignmentCenter )
  ControlSetFormat( _numFld, @"0123456789", YES, 4, 0 )
  WindowMakeFirstResponder( _window, _numFld )
end fn


void local fn PathDraw( path as BezierPathRef, lines as CFStringRef, x as CGFloat, y as CGFloat )
  CGPoint pt1, pt2
  long i
  for i = 0 to 4
    if ( intval(mid(lines,i,1)) )
      select ( i )
        case 0
          pt1 = fn CGPointMake( x + _lineLength, y )
          pt2 = fn CGPointMake( x + _lineLength, y + _lineLength )
        case 1
          pt1 = fn CGPointMake( x, y + _lineLength )
          pt2 = fn CGPointMake( x + _lineLength, y )
        case 2
          pt1 = fn CGPointMake( x, y )
          pt2 = fn CGPointMake( x + _lineLength, y + _lineLength )
        case 3
          pt1 = fn CGPointMake( x, y + _lineLength )
          pt2 = fn CGPointMake( x + _lineLength, y + _lineLength )
        case 4
          pt1 = fn CGPointMake( x, y )
          pt2 = fn CGPointMake( x + _lineLength, y )
      end select
      BezierPathMoveToPoint( path, pt1 )
      BezierPathLineToPoint( path, pt2 )
    end if
  next
end fn


void local fn ViewDrawRect
  CFArrayRef lines = @[@"00001",@"00010",@"00100",@"01000",@"01001",@"10000",@"10001",@"10010",@"10011"]
  CFStringRef numString = fn ViewProperty( _numView, @"num" )
  if ( numString )
    CGFloat x = 38, y = 20
    
    long i
    for i = 0 to 3
      BezierPathRef path = fn BezierPathWithRect( fn ViewBounds(_numView) )
      BezierPathMoveToPoint( path, fn CGPointMake( x, y ) )
      BezierPathLineToPoint( path, fn CGPointMake( x, y + _numHeight ) )
      
      long num = intval( mid( numString, i, 1 ) )
      if ( num )
        fn PathDraw( path, lines[num-1], x, y )
        if ( i < 3 )
          CGFloat xScale = 1.0, yScale = 1.0
          select ( i )
            case 0 : xScale = -1.0 : yScale = -1.0 // 1000
            case 1 : yScale = -1.0                 // 100
            case 2 : xScale = -1.0                 // 10
          end select
          
          CGRect bounds = fn BezierPathBounds( path )
          AffineTransformRef tx = fn AffineTransformInit
          AffineTransformScaleXY( tx, xScale, yScale )
          if ( xScale < 0.0 ) then AffineTransformTranslate( tx, -bounds.origin.x-bounds.size.width, 0.0 )
          if ( yScale < 0.0 ) then AffineTransformTranslate( tx, 0.0, -bounds.size.height )
          
          BezierPathTransformUsingAffineTranform( path, tx )
        end if
      end if
      
      BezierPathStroke( path )
    next
  end if
end fn


void local fn DrawAction
  CFStringRef string = fn StringWithFormat( @"%.4ld", fn ControlIntegerValue( _numFld ) )
  ViewSetProperty( _numView, @"num", string )
  ViewSetNeedsDisplay( _numView )
end fn


void local fn DoAppEvent( ev as long )
  select ( ev )
    case _appDidFinishLaunching
      fn BuildWindow
      fn DrawAction
    case _appShouldTerminateAfterLastWindowClosed : AppEventSetBool(YES)
  end select
end fn


void local fn DoDialog( ev as long, tag as long, wnd as long )
  select ( ev )
    case _btnClick
      select ( tag )
        case _numFld : fn DrawAction
      end select
      
    case _viewDrawRect
      select ( tag )
        case _numView : fn ViewDrawRect
      end select
  end select
end fn


on appevent fn DoAppEvent
on dialog fn DoDialog

HandleEvents

Go

Translation of: Wren
package main

import "fmt"

var n = make([][]string, 15)

func initN() {
    for i := 0; i < 15; i++ {
        n[i] = make([]string, 11)
        for j := 0; j < 11; j++ {
            n[i][j] = " "
        }
        n[i][5] = "x"
    }
}

func horiz(c1, c2, r int) {
    for c := c1; c <= c2; c++ {
        n[r][c] = "x"
    }
}

func verti(r1, r2, c int) {
    for r := r1; r <= r2; r++ {
        n[r][c] = "x"
    }
}

func diagd(c1, c2, r int) {
    for c := c1; c <= c2; c++ {
        n[r+c-c1][c] = "x"
    }
}

func diagu(c1, c2, r int) {
    for c := c1; c <= c2; c++ {
        n[r-c+c1][c] = "x"
    }
}

var draw map[int]func() // map contains recursive closures

func initDraw() {
    draw = map[int]func(){
        1: func() { horiz(6, 10, 0) },
        2: func() { horiz(6, 10, 4) },
        3: func() { diagd(6, 10, 0) },
        4: func() { diagu(6, 10, 4) },
        5: func() { draw[1](); draw[4]() },
        6: func() { verti(0, 4, 10) },
        7: func() { draw[1](); draw[6]() },
        8: func() { draw[2](); draw[6]() },
        9: func() { draw[1](); draw[8]() },

        10: func() { horiz(0, 4, 0) },
        20: func() { horiz(0, 4, 4) },
        30: func() { diagu(0, 4, 4) },
        40: func() { diagd(0, 4, 0) },
        50: func() { draw[10](); draw[40]() },
        60: func() { verti(0, 4, 0) },
        70: func() { draw[10](); draw[60]() },
        80: func() { draw[20](); draw[60]() },
        90: func() { draw[10](); draw[80]() },

        100: func() { horiz(6, 10, 14) },
        200: func() { horiz(6, 10, 10) },
        300: func() { diagu(6, 10, 14) },
        400: func() { diagd(6, 10, 10) },
        500: func() { draw[100](); draw[400]() },
        600: func() { verti(10, 14, 10) },
        700: func() { draw[100](); draw[600]() },
        800: func() { draw[200](); draw[600]() },
        900: func() { draw[100](); draw[800]() },

        1000: func() { horiz(0, 4, 14) },
        2000: func() { horiz(0, 4, 10) },
        3000: func() { diagd(0, 4, 10) },
        4000: func() { diagu(0, 4, 14) },
        5000: func() { draw[1000](); draw[4000]() },
        6000: func() { verti(10, 14, 0) },
        7000: func() { draw[1000](); draw[6000]() },
        8000: func() { draw[2000](); draw[6000]() },
        9000: func() { draw[1000](); draw[8000]() },
    }
}

func printNumeral() {
    for i := 0; i < 15; i++ {
        for j := 0; j < 11; j++ {
            fmt.Printf("%s ", n[i][j])
        }
        fmt.Println()
    }
    fmt.Println()
}

func main() {
    initDraw()
    numbers := []int{0, 1, 20, 300, 4000, 5555, 6789, 9999}
    for _, number := range numbers {
        initN()
        fmt.Printf("%d:\n", number)
        thousands := number / 1000
        number %= 1000
        hundreds := number / 100
        number %= 100
        tens := number / 10
        ones := number % 10
        if thousands > 0 {
            draw[thousands*1000]()
        }
        if hundreds > 0 {
            draw[hundreds*100]()
        }
        if tens > 0 {
            draw[tens*10]()
        }
        if ones > 0 {
            draw[ones]()
        }
        printNumeral()
    }
}
Output:
Same as Wren example.

J

Program writes a scalable vector graphics file containing all composable numbers. J code is alongside the original python source. Save as file jc.ijs, then invoke in a j session

   main'jc.svg'[load'jc.ijs'
open browser to /tmp/jc.svg

The rc verb writes RC=. 0 1 20 300 666 4000 5555 6789

NB. http://rosettacode.org/wiki/Cistercian_numerals
NB. converted from
NB. https://scipython.com/blog/cistercian-numerals/

Dyad=: [: :

NB. numeric_vector format 'python {} string'
format=: ''&$: :([: ; (a: , [: ":&.> [) ,. '{}' ([ (E. <@}.;._1 ]) ,) ])  NB. literals x should be boxed

pwd=:1!:43
rm=: 1!:55@boxopen ::empty
print=: echo@[ NB. debug
print=: (1!:3~,&LF)~ Dyad
open=: 1!:21
close=: 1!:22

NB.# http://en.kpartner.kr/data/warrant-check-pzmwqyk/qrf56.php?3fff1d=cistercian-numbers-unicode
NB.
NB.# The paths to create the digits 1–9 in the "units" position.
NB.d_paths = {
NB.(0, 1): ((1, 0), (2, 0)),
NB.(0, 2): ((1, 1), (2, 1)),
NB.(0, 3): ((1, 0), (2, 1)),
NB.(0, 4): ((1, 1), (2, 0)),
NB.(0, 5): ((1, 1), (2, 0), (1, 0)),
NB.(0, 6): ((2, 0), (2, 1)),
NB.(0, 7): ((1, 0), (2, 0), (2, 1)),
NB.(0, 8): ((1, 1), (2, 1), (2, 0)),
NB.(0, 9): ((1, 1), (2, 1), (2, 0), (1, 0)),
NB.}
NB.# Generate the paths for the digits in the 10s, 100s and 1000s position by
NB.# reflection.
NB.for i in range(1, 10):
NB.    d_paths[(1, i)] = [(2-x, y) for x, y in d_paths[(0, i)]]
NB.    d_paths[(2, i)] = [(x, 3-y) for x, y in d_paths[(0, i)]]
NB.    d_paths[(3, i)] = [(2-x, 3-y) for x, y in d_paths[(0, i)]]
NB.
d_paths=: _2[\L:0]((1, 0), (2, 0));((1, 1), (2, 1));((1, 0), (2, 1));((1, 1), (2, 0));((1, 1), (2, 0), (1, 0));((2, 0), (2, 1));((1, 0), (2, 0), (2, 1));((1, 1), (2, 1), (2, 0));((1, 1), (2, 1), (2, 0), (1, 0))
d_paths=: (, ((2-[),])/"1 L:0 , (,3&-)/"1 L:0 , ((2-[),(3-]))/"1 L:0) d_paths
d_paths=: , a: ,. _9]\ d_paths  NB. adjust indexing
NB.echo d_paths NB. test

NB.def transform(x, y, dx, dy, sc):
NB.    """Transform the coordinates (x, y) into the scaled, displaced system."""
NB.    return x*sc + dx, y*sc + dy
NB.
transform=: (] p.~ [: (2&{. (,.) 2 $ 2&}.) [) Dyad  NB. (dx dy sx [sy]) transform (x y)

NB.def get_path(i, d):
NB.    """Return the SVG path to render the digit d in decimal position i."""
NB.    if d == 0:
NB.        return
NB.    path = d_paths[(i, d)]
NB.    return 'M{},{} '.format(*transform(*path[0], *tprms)) + ' '.join(
NB.                ['L{},{}'.format(*transform(*xy, *tprms)) for xy in path[1:]])
NB.
get_path=: 3 :0
 'i d'=. y
 if. d do.
  path=. d_paths {::~ 10 #. y
  result=. 'M{},{} 'format~ TPRMS transform {. path
  result=. result , }: , ' ' ,.~ 'L{},{}'format"1~TPRMS transform"1 }. path
 else.
  ''
 end.
) 

NB.def make_digit(i, d):
NB.    """Output the SVG path element for digit d in decimal position i."""
NB.    print('<path d="{}"/>'.format(get_path(i, d)), file=fo)
NB.
make_digit=: (print~ (('<path d="{}"/>') (format~ <) get_path)) Dyad NB. fo make_digit n

NB.def make_stave():
NB.    """Output the SVG line element for the vertical stave."""
NB.    x1, y1 = transform(1, 0, *tprms)
NB.    x2, y2 = transform(1, 3, *tprms)
NB.    print('<line x1="{}" y1="{}" x2="{}" y2="{}"/>'.format(x1, y1, x2, y2),
NB.          file=fo)
make_stave=: 3 :'y print~ ''<line x1="{}" y1="{}" x2="{}" y2="{}"/>'' format~ , TPRMS (transform"1) 1 0,:1 3'

NB.def svg_preamble(fo):
NB.    """Write the SVG preamble, including the styles."""
NB.
NB.    # Set the path stroke-width appropriate to the scale.
NB.    stroke_width = max(1.5, tprms[2] / 5)
NB.    print("""<?xml version="1.0" encoding="utf-8"?>
NB.<svg xmlns="http://www.w3.org/2000/svg"
NB.     xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2005" >
NB.<defs>
NB.<style type="text/css"><![CDATA[
NB.line, path {
NB.  stroke: black;
NB.  stroke-width: %d;
NB.  stroke-linecap: square;
NB.}
NB.path {
NB.  fill: none;
NB.}
NB.]]>
NB.</style>
NB.</defs>
NB.""" % stroke_width, file=fo)
NB.
PREAMBLE=: 0 :0
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink" width="2000" height="2005" >
<defs>
<style type="text/css"><![CDATA[
line, path {
  stroke: black;
  stroke-width: {};
  stroke-linecap: square;
}
path {
  fill: none;
}
]]>
</style>
</defs>
)

svg_preamble=: 3 :'(PREAMBLE format~ 1.5 >. 5 *inv 2 { TPRMS) print y'

NB.def make_numeral(n, fo):
NB.    """Output the SVG for the number n using the current transform."""
NB.    make_stave()
NB.    for i, s_d in enumerate(str(n)[::-1]):
NB.        make_digit(i, int(s_d))
NB.
make_numeral=: 4 :0
 fo=. x
 n=. y
 make_stave fo
 if. y do.
  fo make_digit"1 (,.~ i.@#) |. 10 #.inv n
 end.
)
 
NB.# Transform parameters: dx, dy, scale.
NB.tprms = [5, 5, 5]
NB.
NB.with open('all_cistercian_numerals.svg', 'w') as fo:
NB.    svg_preamble(fo)
NB.    for i in range(10000):
NB.        # Locate this number at the position dx, dy = tprms[:2].
NB.        tprms[0] = 15 * (i % 125) + 5
NB.        tprms[1] = 25 * (i // 125) + 5
NB.        make_numeral(i, fo)
NB.    print("""</svg>""", file=fo)
main=: 3 :0 ::('Use: main ''filename.svg'''"_)
 TPRMS=: 5 5 5
 rm<y
 fo=. open<y
 svg_preamble fo
 for_i. i. 10000 do.
  TPRMS=: (5 ,~ (5 + 15 * 125 | ]) , 5 + 25 * [: (<.) 125 *^:_1 ]) i
  fo make_numeral i
 end.
 '</svg>' print fo
 empty close fo
 'open browser to {}/{}' format~ (pwd'') ; y
)
rc=: 3 :0 ::('Use: rc ''filename.svg'''"_)
 scale=. 5
 TPRMS=: 5 5 , scale
 rm<y
 fo=. open<y
 svg_preamble fo
 RC=. 0 1 20 300 666 4000 5555 6789
 echo 'writing {}' format~ < RC 
 for_k. (,.~ i.@#) RC do.
  'j i'=. k
  TPRMS=: (scale ,~ (5 + scale * 15 * 125 | ]) , 5 + scale * 25 * [: (<.) 125 *^:_1 ]) j
  fo make_numeral i
 end.
 '</svg>' print fo
 empty close fo
 'open browser to {}{}{}' format~ (pwd'') ; PATHJSEP_j_ ; y
)

Java

Translation of: Kotlin
import java.util.Arrays;
import java.util.List;

public class Cistercian {
    private static final int SIZE = 15;
    private final char[][] canvas = new char[SIZE][SIZE];

    public Cistercian(int n) {
        initN();
        draw(n);
    }

    public void initN() {
        for (var row : canvas) {
            Arrays.fill(row, ' ');
            row[5] = 'x';
        }
    }

    private void horizontal(int c1, int c2, int r) {
        for (int c = c1; c <= c2; c++) {
            canvas[r][c] = 'x';
        }
    }

    private void vertical(int r1, int r2, int c) {
        for (int r = r1; r <= r2; r++) {
            canvas[r][c] = 'x';
        }
    }

    private void diagd(int c1, int c2, int r) {
        for (int c = c1; c <= c2; c++) {
            canvas[r + c - c1][c] = 'x';
        }
    }

    private void diagu(int c1, int c2, int r) {
        for (int c = c1; c <= c2; c++) {
            canvas[r - c + c1][c] = 'x';
        }
    }

    private void draw(int v) {
        var thousands = v / 1000;
        v %= 1000;

        var hundreds = v / 100;
        v %= 100;

        var tens = v / 10;
        var ones = v % 10;

        drawPart(1000 * thousands);
        drawPart(100 * hundreds);
        drawPart(10 * tens);
        drawPart(ones);
    }

    private void drawPart(int v) {
        switch (v) {
            case 1:
                horizontal(6, 10, 0);
                break;
            case 2:
                horizontal(6, 10, 4);
                break;
            case 3:
                diagd(6, 10, 0);
                break;
            case 4:
                diagu(6, 10, 4);
                break;
            case 5:
                drawPart(1);
                drawPart(4);
                break;
            case 6:
                vertical(0, 4, 10);
                break;
            case 7:
                drawPart(1);
                drawPart(6);
                break;
            case 8:
                drawPart(2);
                drawPart(6);
                break;
            case 9:
                drawPart(1);
                drawPart(8);
                break;

            case 10:
                horizontal(0, 4, 0);
                break;
            case 20:
                horizontal(0, 4, 4);
                break;
            case 30:
                diagu(0, 4, 4);
                break;
            case 40:
                diagd(0, 4, 0);
                break;
            case 50:
                drawPart(10);
                drawPart(40);
                break;
            case 60:
                vertical(0, 4, 0);
                break;
            case 70:
                drawPart(10);
                drawPart(60);
                break;
            case 80:
                drawPart(20);
                drawPart(60);
                break;
            case 90:
                drawPart(10);
                drawPart(80);
                break;

            case 100:
                horizontal(6, 10, 14);
                break;
            case 200:
                horizontal(6, 10, 10);
                break;
            case 300:
                diagu(6, 10, 14);
                break;
            case 400:
                diagd(6, 10, 10);
                break;
            case 500:
                drawPart(100);
                drawPart(400);
                break;
            case 600:
                vertical(10, 14, 10);
                break;
            case 700:
                drawPart(100);
                drawPart(600);
                break;
            case 800:
                drawPart(200);
                drawPart(600);
                break;
            case 900:
                drawPart(100);
                drawPart(800);
                break;

            case 1000:
                horizontal(0, 4, 14);
                break;
            case 2000:
                horizontal(0, 4, 10);
                break;
            case 3000:
                diagd(0, 4, 10);
                break;
            case 4000:
                diagu(0, 4, 14);
                break;
            case 5000:
                drawPart(1000);
                drawPart(4000);
                break;
            case 6000:
                vertical(10, 14, 0);
                break;
            case 7000:
                drawPart(1000);
                drawPart(6000);
                break;
            case 8000:
                drawPart(2000);
                drawPart(6000);
                break;
            case 9000:
                drawPart(1000);
                drawPart(8000);
                break;

        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (var row : canvas) {
            builder.append(row);
            builder.append('\n');
        }
        return builder.toString();
    }

    public static void main(String[] args) {
        for (int number : List.of(0, 1, 20, 300, 4000, 5555, 6789, 9999)) {
            System.out.printf("%d:\n", number);
            var c = new Cistercian(number);
            System.out.println(c);
        }
    }
}
Output:
0:
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         

1:
     xxxxxx    
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         

20:
     x         
     x         
     x         
     x         
xxxxxx         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         

300:
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x    x    
     x   x     
     x  x      
     x x       
     xx        

4000:
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
    xx         
   x x         
  x  x         
 x   x         
x    x         

5555:
xxxxxxxxxxx    
 x   x   x     
  x  x  x      
   x x x       
    xxx        
     x         
     x         
     x         
     x         
     x         
    xxx        
   x x x       
  x  x  x      
 x   x   x     
xxxxxxxxxxx    

6789:
x    xxxxxx    
x    x    x    
x    x    x    
x    x    x    
xxxxxxxxxxx    
     x         
     x         
     x         
     x         
     x         
x    x    x    
x    x    x    
x    x    x    
x    x    x    
x    xxxxxx    

9999:
xxxxxxxxxxx    
x    x    x    
x    x    x    
x    x    x    
xxxxxxxxxxx    
     x         
     x         
     x         
     x         
     x         
xxxxxxxxxxx    
x    x    x    
x    x    x    
x    x    x    
xxxxxxxxxxx    

JavaScript

Using a canvas.

// html
document.write(`
  <p><input id="num" type="number" min="0" max="9999" value="0" onchange="showCist()"></p>
  <p><canvas id="cist" width="200" height="300"></canvas></p>
  <p> <!-- EXAMPLES (can be deleted for normal use) -->
    <button onclick="set(0)">0</button>
    <button onclick="set(1)">1</button>
    <button onclick="set(20)">20</button>
    <button onclick="set(300)">300</button>
    <button onclick="set(4000)">4000</button>
    <button onclick="set(5555)">5555</button>
    <button onclick="set(6789)">6789</button>
    <button onclick="set(Math.floor(Math.random()*1e4))">Random</button>
  </p>
`);

// to show given examples
// can be deleted for normal use
function set(num) {
  document.getElementById('num').value = num;
  showCist();
}

const SW = 10; // stroke width
let canvas = document.getElementById('cist'),
    cx = canvas.getContext('2d');

function showCist() {
  // reset canvas
  cx.clearRect(0, 0, canvas.width, canvas.height);
  cx.lineWidth = SW;
  cx.beginPath();
  cx.moveTo(100, 0+.5*SW);
  cx.lineTo(100, 300-.5*SW);
  cx.stroke();

  let num = document.getElementById('num').value;
  while (num.length < 4) num = '0' + num;  // fills leading zeros to $num

  /***********************\
  |        POINTS:        |
  | ********************* |
  |                       |
  |     a --- b --- c     |
  |     |     |     |     |
  |     d --- e --- f     |
  |     |     |     |     |
  |     g --- h --- i     |
  |     |     |     |     |
  |     j --- k --- l     |
  |                       |
  \***********************/
  let
  a = [0+SW,   0+SW],   b = [100,   0+SW],   c = [200-SW,   0+SW],
  d = [0+SW,    100],   e = [100,    100],   f = [200-SW,    100],
  g = [0+SW,    200],   h = [100,    200],   i = [200-SW,    200],
  j = [0+SW, 300-SW],   k = [100, 300-SW],   l = [200-SW, 300-SW];

  function draw() {
    let x = 1;
    cx.beginPath();
    cx.moveTo(arguments[0][0], arguments[0][1]);
    while (x < arguments.length) {
      cx.lineTo(arguments[x][0], arguments[x][1]);
      x++;
    }
    cx.stroke();
  }

  // 1000s
  switch (num[0]) {
    case '1': draw(j, k);       break;       case '2': draw(g, h);    break;
    case '3': draw(g, k);       break;       case '4': draw(j, h);    break;
    case '5': draw(k, j, h);    break;       case '6': draw(g, j);    break;
    case '7': draw(g, j, k);    break;       case '8': draw(j, g, h); break;
    case '9': draw(h, g, j, k); break;
  }
  // 100s
  switch (num[1]) {
    case '1': draw(k, l);       break;       case '2': draw(h, i);    break;
    case '3': draw(k, i);       break;       case '4': draw(h, l);    break;
    case '5': draw(h, l, k);    break;       case '6': draw(i, l);    break;
    case '7': draw(k, l, i);    break;       case '8': draw(h, i, l); break;
    case '9': draw(h, i, l, k); break;
  }
  // 10s
  switch (num[2]) {
    case '1': draw(a, b);       break;       case '2': draw(d, e);    break;
    case '3': draw(d, b);       break;       case '4': draw(a, e);    break;
    case '5': draw(b, a, e);    break;       case '6': draw(a, d);    break;
    case '7': draw(d, a, b);    break;       case '8': draw(a, d, e); break;
    case '9': draw(b, a, d, e); break;
  }
  // 1s
  switch (num[3]) {
    case '1': draw(b, c);       break;       case '2': draw(e, f);    break;
    case '3': draw(b, f);       break;       case '4': draw(e, c);    break;
    case '5': draw(b, c, e);    break;       case '6': draw(c, f);    break;
    case '7': draw(b, c, f);    break;       case '8': draw(e, f, c); break;
    case '9': draw(b, c, f, e); break;
  }
}
Output:

https://jsfiddle.net/43tsmn9z

jq

Works with jq, the C implementation of jq

Works with gojq, the Go implementation of jq

Adapted from Wren

### Generic function
# Replace whatever is at .[$i:$i+1] with $x.
# The input and $x should be of the same type - strings or arrays.
def replace($i; $x): .[:$i] + $x + .[$i+1:];

### Cistercian numerals

# The canvas: an array of strings
def canvas:
  (" " * 11) as $row
  | [range(0; 15) | $row | replace(5; "x")];
  
def horiz($c1; $c2; $r):
  reduce range($c1; $c2+1) as $c (.; .[$r] |= replace($c; "x"));

def verti($r1; $r2; $c):
  reduce range($r1; $r2+1) as $r (.; .[$r] |= replace($c; "x"));

def diagd($c1; $c2; $r):
  reduce range($c1; $c2+1) as $c (.; .[$r+$c-$c1] |= replace($c;"x"));

def diagu($c1; $c2; $r):
  reduce range($c1; $c2+1) as $c (.; .[$r-$c+$c1] |= replace($c; "x"));

# input: the canvas
def draw($n):
  if   $n == 0 then .
  elif $n == 1 then horiz(6; 10; 0)
  elif $n == 2 then horiz(6; 10; 4)
  elif $n == 3 then diagd(6; 10; 0)
  elif $n == 4 then diagu(6; 10; 4)
  elif $n == 5 then draw(1) | draw(4)
  elif $n == 6 then verti(0; 4; 10)
  elif $n == 7 then draw(1) | draw(6)
  elif $n == 8 then draw(2) | draw(6)
  elif $n == 9 then draw(1) | draw(8)
  elif $n == 10 then horiz(0; 4; 0)
  elif $n == 20 then horiz(0; 4; 4)
  elif $n == 30 then diagu(0; 4; 4)
  elif $n == 40 then diagd(0; 4; 0) 
  elif $n == 50 then draw(10) | draw(40)
  elif $n == 60 then verti(0; 4; 0) 
  elif $n == 70 then draw(10) | draw(60)
  elif $n == 80 then draw(20) | draw(60)
  elif $n == 90 then draw(10) | draw(80)
  elif $n == 100 then horiz(6; 10; 14)
  elif $n == 200 then horiz(6; 10; 10)
  elif $n == 300 then diagu(6; 10; 14)
  elif $n == 400 then diagd(6; 10; 10)
  elif $n == 500 then draw(100) | draw(400)
  elif $n == 600 then verti(10; 14; 10)
  elif $n == 700 then draw(100) | draw(600)
  elif $n == 800 then draw(200) | draw(600)
  elif $n == 900 then draw(100) | draw(800)
  elif $n == 1000 then horiz(0; 4; 14)
  elif $n == 2000 then horiz(0; 4; 10)
  elif $n == 3000 then diagd(0; 4; 10)
  elif $n == 4000 then diagu(0; 4; 14)
  elif $n == 5000 then draw(1000) | draw(4000)
  elif $n == 6000 then verti(10; 14; 0)
  elif $n == 7000 then draw(1000) | draw(6000)
  elif $n == 8000 then draw(2000) | draw(6000)
  elif $n == 9000 then draw(1000) | draw(8000)
  else "unable to draw \(.)" | error
  end;

def cistercian:
  (./1000|floor) as $thousands
  | (. % 1000) as $n
  | ($n/100|floor) as $hundreds
  | ($n % 100) as $n
  | ($n/10|floor) as $tens
  | ($n % 10) as $ones
  | "\(.):",
    ( canvas
     | draw($thousands*1000)
     | draw($hundreds*100)
     | draw($tens*10)
     | draw($ones)
     | .[] ),
    "" ;

0, 1, 20, 300, 4000, 5555, 6789, 9999
| cistercian
Output:
0:
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     

1:
     xxxxxx
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     

20:
     x     
     x     
     x     
     x     
xxxxxx     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     

300:
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x    x
     x   x 
     x  x  
     x x   
     xx    

4000:
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
     x     
    xx     
   x x     
  x  x     
 x   x     
x    x     

5555:
xxxxxxxxxxx
 x   x   x 
  x  x  x  
   x x x   
    xxx    
     x     
     x     
     x     
     x     
     x     
    xxx    
   x x x   
  x  x  x  
 x   x   x 
xxxxxxxxxxx

6789:
x    xxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x     
     x     
     x     
     x     
     x     
x    x    x
x    x    x
x    x    x
x    x    x
x    xxxxxx

9999:
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x     
     x     
     x     
     x     
     x     
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx

Julia

Gtk graphic version.

using Gtk, Cairo

const can = GtkCanvas(800, 100)
const win = GtkWindow(can, "Canvas")
const numbers = [0, 1, 20, 300, 4000, 5555, 6789, 8123]

function drawcnum(ctx, xypairs)
    move_to(ctx, xypairs[1][1], xypairs[1][2])
    for p in xypairs[2:end]
        line_to(ctx, p[1], p[2])
    end
    stroke(ctx)
end

@guarded draw(can) do widget
    ctx = getgc(can)
    hlen, wlen, len = height(can), width(can), length(numbers)
    halfwspan, thirdcolspan, voffset = wlen ÷ (len * 2), wlen ÷ (len * 3), hlen ÷ 8
    set_source_rgb(ctx, 0, 0, 2550)
    for (i, n) in enumerate(numbers)
        # paint vertical as width 2 rectangle
        x = halfwspan * (2 * i - 1)
        rectangle(ctx, x, voffset, 2, hlen - 2 * voffset)
        stroke(ctx)
        # determine quadrant and draw numeral lines there
        dig = [(10^(i - 1), m) for (i, m) in enumerate(digits(n))]
        for (d, m) in dig
            y, dx, dy = (d == 1) ? (voffset, thirdcolspan, thirdcolspan) :
                (d == 10) ? (voffset, -thirdcolspan, thirdcolspan) :
                (d == 100) ? (hlen - voffset, thirdcolspan, -thirdcolspan) :
                (hlen - voffset, -thirdcolspan, -thirdcolspan)
            m == 1 && drawcnum(ctx, [[x, y], [x + dx, y]])
            m == 2 && drawcnum(ctx, [[x, y + dy], [x + dx, y + dy]])
            m == 3 && drawcnum(ctx, [[x, y], [x + dx, y + dy]])
            m == 4 && drawcnum(ctx, [[x, y + dy], [x + dx, y]])
            m == 5 && drawcnum(ctx, [[x, y + dy], [x + dx, y], [x, y]])
            m == 6 && drawcnum(ctx, [[x + dx, y], [x + dx, y + dy]])
            m == 7 && drawcnum(ctx, [[x, y], [x + dx, y], [x + dx, y + dy]])
            m == 8 && drawcnum(ctx, [[x, y + dy], [x + dx, y + dy], [x + dx, y]])
            m == 9 && drawcnum(ctx, [[x, y], [x + dx, y], [x + dx, y + dy], [x, y + dy]])
        end
        move_to(ctx, x - halfwspan ÷ 6, hlen - 4)
        Cairo.show_text(ctx, string(n))
        stroke(ctx)
    end
end

function mooncipher()
    draw(can)
    cond = Condition()
    endit(w) = notify(cond)
    signal_connect(endit, win, :destroy)
    show(can)
    wait(cond)
end

mooncipher()

Kotlin

Translation of: C++
import java.io.StringWriter

class Cistercian() {
    constructor(number: Int) : this() {
        draw(number)
    }

    private val size = 15
    private var canvas = Array(size) { Array(size) { ' ' } }

    init {
        initN()
    }

    private fun initN() {
        for (row in canvas) {
            row.fill(' ')
            row[5] = 'x'
        }
    }

    private fun horizontal(c1: Int, c2: Int, r: Int) {
        for (c in c1..c2) {
            canvas[r][c] = 'x'
        }
    }

    private fun vertical(r1: Int, r2: Int, c: Int) {
        for (r in r1..r2) {
            canvas[r][c] = 'x'
        }
    }

    private fun diagd(c1: Int, c2: Int, r: Int) {
        for (c in c1..c2) {
            canvas[r + c - c1][c] = 'x'
        }
    }

    private fun diagu(c1: Int, c2: Int, r: Int) {
        for (c in c1..c2) {
            canvas[r - c + c1][c] = 'x'
        }
    }

    private fun drawPart(v: Int) {
        when (v) {
            1 -> {
                horizontal(6, 10, 0)
            }
            2 -> {
                horizontal(6, 10, 4)
            }
            3 -> {
                diagd(6, 10, 0)
            }
            4 -> {
                diagu(6, 10, 4)
            }
            5 -> {
                drawPart(1)
                drawPart(4)
            }
            6 -> {
                vertical(0, 4, 10)
            }
            7 -> {
                drawPart(1)
                drawPart(6)
            }
            8 -> {
                drawPart(2)
                drawPart(6)
            }
            9 -> {
                drawPart(1)
                drawPart(8)
            }

            10 -> {
                horizontal(0, 4, 0)
            }
            20 -> {
                horizontal(0, 4, 4)
            }
            30 -> {
                diagu(0, 4, 4)
            }
            40 -> {
                diagd(0, 4, 0)
            }
            50 -> {
                drawPart(10)
                drawPart(40)
            }
            60 -> {
                vertical(0, 4, 0)
            }
            70 -> {
                drawPart(10)
                drawPart(60)
            }
            80 -> {
                drawPart(20)
                drawPart(60)
            }
            90 -> {
                drawPart(10)
                drawPart(80)
            }

            100 -> {
                horizontal(6, 10, 14)
            }
            200 -> {
                horizontal(6, 10, 10)
            }
            300 -> {
                diagu(6, 10, 14)
            }
            400 -> {
                diagd(6, 10, 10)
            }
            500 -> {
                drawPart(100)
                drawPart(400)
            }
            600 -> {
                vertical(10, 14, 10)
            }
            700 -> {
                drawPart(100)
                drawPart(600)
            }
            800 -> {
                drawPart(200)
                drawPart(600)
            }
            900 -> {
                drawPart(100)
                drawPart(800)
            }

            1000 -> {
                horizontal(0, 4, 14)
            }
            2000 -> {
                horizontal(0, 4, 10)
            }
            3000 -> {
                diagd(0, 4, 10)
            }
            4000 -> {
                diagu(0, 4, 14)
            }
            5000 -> {
                drawPart(1000)
                drawPart(4000)
            }
            6000 -> {
                vertical(10, 14, 0)
            }
            7000 -> {
                drawPart(1000)
                drawPart(6000)
            }
            8000 -> {
                drawPart(2000)
                drawPart(6000)
            }
            9000 -> {
                drawPart(1000)
                drawPart(8000)
            }
        }
    }

    private fun draw(v: Int) {
        var v2 = v

        val thousands = v2 / 1000
        v2 %= 1000

        val hundreds = v2 / 100
        v2 %= 100

        val tens = v2 / 10
        val ones = v % 10

        if (thousands > 0) {
            drawPart(1000 * thousands)
        }
        if (hundreds > 0) {
            drawPart(100 * hundreds)
        }
        if (tens > 0) {
            drawPart(10 * tens)
        }
        if (ones > 0) {
            drawPart(ones)
        }
    }

    override fun toString(): String {
        val sw = StringWriter()
        for (row in canvas) {
            for (cell in row) {
                sw.append(cell)
            }
            sw.appendLine()
        }
        return sw.toString()
    }
}

fun main() {
    for (number in arrayOf(0, 1, 20, 300, 4000, 5555, 6789, 9999)) {
        println("$number:")

        val c = Cistercian(number)
        println(c)
    }

}
Output:
0:
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         

1:
     xxxxxx    
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         

20:
     x         
     x         
     x         
     x         
xxxxxx         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         

300:
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x    x    
     x   x     
     x  x      
     x x       
     xx        

4000:
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
    xx         
   x x         
  x  x         
 x   x         
x    x         

5555:
xxxxxxxxxxx    
 x   x   x     
  x  x  x      
   x x x       
    xxx        
     x         
     x         
     x         
     x         
     x         
    xxx        
   x x x       
  x  x  x      
 x   x   x     
xxxxxxxxxxx    

6789:
x    xxxxxx    
x    x    x    
x    x    x    
x    x    x    
xxxxxxxxxxx    
     x         
     x         
     x         
     x         
     x         
x    x    x    
x    x    x    
x    x    x    
x    x    x    
x    xxxxxx    

9999:
xxxxxxxxxxx    
x    x    x    
x    x    x    
x    x    x    
xxxxxxxxxxx    
     x         
     x         
     x         
     x         
     x         
xxxxxxxxxxx    
x    x    x    
x    x    x    
x    x    x    
xxxxxxxxxxx    

Lua

Translation of: Go
function initN()
    local n = {}
    for i=1,15 do
        n[i] = {}
        for j=1,11 do
            n[i][j] = " "
        end
        n[i][6] = "x"
    end
    return n
end

function horiz(n, c1, c2, r)
    for c=c1,c2 do
        n[r+1][c+1] = "x"
    end
end

function verti(n, r1, r2, c)
    for r=r1,r2 do
        n[r+1][c+1] = "x"
    end
end

function diagd(n, c1, c2, r)
    for c=c1,c2 do
        n[r+c-c1+1][c+1] = "x"
    end
end

function diagu(n, c1, c2, r)
    for c=c1,c2 do
        n[r-c+c1+1][c+1] = "x"
    end
end

function initDraw()
    local draw = {}

    draw[1] = function(n) horiz(n, 6, 10, 0) end
    draw[2] = function(n) horiz(n, 6, 10, 4) end
    draw[3] = function(n) diagd(n, 6, 10, 0) end
    draw[4] = function(n) diagu(n, 6, 10, 4) end
    draw[5] = function(n) draw[1](n) draw[4](n) end
    draw[6] = function(n) verti(n, 0, 4, 10) end
    draw[7] = function(n) draw[1](n) draw[6](n) end
    draw[8] = function(n) draw[2](n) draw[6](n) end
    draw[9] = function(n) draw[1](n) draw[8](n) end

    draw[10] = function(n) horiz(n, 0, 4, 0) end
    draw[20] = function(n) horiz(n, 0, 4, 4) end
    draw[30] = function(n) diagu(n, 0, 4, 4) end
    draw[40] = function(n) diagd(n, 0, 4, 0) end
    draw[50] = function(n) draw[10](n) draw[40](n) end
    draw[60] = function(n) verti(n, 0, 4, 0) end
    draw[70] = function(n) draw[10](n) draw[60](n) end
    draw[80] = function(n) draw[20](n) draw[60](n) end
    draw[90] = function(n) draw[10](n) draw[80](n) end

    draw[100] = function(n) horiz(n, 6, 10, 14) end
    draw[200] = function(n) horiz(n, 6, 10, 10) end
    draw[300] = function(n) diagu(n, 6, 10, 14) end
    draw[400] = function(n) diagd(n, 6, 10, 10) end
    draw[500] = function(n) draw[100](n) draw[400](n) end
    draw[600] = function(n) verti(n, 10, 14, 10) end
    draw[700] = function(n) draw[100](n) draw[600](n) end
    draw[800] = function(n) draw[200](n) draw[600](n) end
    draw[900] = function(n) draw[100](n) draw[800](n) end

    draw[1000] = function(n) horiz(n, 0, 4, 14) end
    draw[2000] = function(n) horiz(n, 0, 4, 10) end
    draw[3000] = function(n) diagd(n, 0, 4, 10) end
    draw[4000] = function(n) diagu(n, 0, 4, 14) end
    draw[5000] = function(n) draw[1000](n) draw[4000](n) end
    draw[6000] = function(n) verti(n, 10, 14, 0) end
    draw[7000] = function(n) draw[1000](n) draw[6000](n) end
    draw[8000] = function(n) draw[2000](n) draw[6000](n) end
    draw[9000] = function(n) draw[1000](n) draw[8000](n) end

    return draw
end

function printNumeral(n)
    for i,v in pairs(n) do
        for j,w in pairs(v) do
            io.write(w .. " ")
        end
        print()
    end
    print()
end

function main()
    local draw = initDraw()
    for i,number in pairs({0, 1, 20, 300, 4000, 5555, 6789, 9999}) do
        local n = initN()
        print(number..":")
        local thousands = math.floor(number / 1000)
        number = number % 1000
        local hundreds = math.floor(number / 100)
        number = number % 100
        local tens = math.floor(number / 10)
        local ones = number % 10
        if thousands > 0 then
            draw[thousands * 1000](n)
        end
        if hundreds > 0 then
            draw[hundreds * 100](n)
        end
        if tens > 0 then
            draw[tens * 10](n)
        end
        if ones > 0 then
            draw[ones](n)
        end
        printNumeral(n)
    end
end

main()
Output:
0:
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x

1:
          x x x x x x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x

20:
          x
          x
          x
          x
x x x x x x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x

300:
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x         x
          x       x
          x     x
          x   x
          x x

4000:
          x
          x
          x
          x
          x
          x
          x
          x
          x
          x
        x x
      x   x
    x     x
  x       x
x         x

5555:
x x x x x x x x x x x
  x       x       x
    x     x     x
      x   x   x
        x x x
          x
          x
          x
          x
          x
        x x x
      x   x   x
    x     x     x
  x       x       x
x x x x x x x x x x x

6789:
x         x x x x x x
x         x         x
x         x         x
x         x         x
x x x x x x x x x x x
          x
          x
          x
          x
          x
x         x         x
x         x         x
x         x         x
x         x         x
x         x x x x x x

9999:
x x x x x x x x x x x
x         x         x
x         x         x
x         x         x
x x x x x x x x x x x
          x
          x
          x
          x
          x
x x x x x x x x x x x
x         x         x
x         x         x
x         x         x
x x x x x x x x x x x

Mathematica/Wolfram Language

ClearAll[CistercianNumberEncodeHelper, CistercianNumberEncode]
\[Delta] = 0.25;
CistercianNumberEncodeHelper[0] := {}
CistercianNumberEncodeHelper[1] := Line[{{0, 1}, {\[Delta], 1}}]
CistercianNumberEncodeHelper[2] := Line[{{0, 1 - \[Delta]}, {\[Delta], 1 - \[Delta]}}]
CistercianNumberEncodeHelper[3] := Line[{{0, 1}, {\[Delta], 1 - \[Delta]}}]
CistercianNumberEncodeHelper[4] := Line[{{0, 1 - \[Delta]}, {\[Delta], 1}}]
CistercianNumberEncodeHelper[5] := Line[{{0, 1 - \[Delta]}, {\[Delta], 1}, {0, 1}}]
CistercianNumberEncodeHelper[6] := Line[{{\[Delta], 1 - \[Delta]}, {\[Delta], 1}}]
CistercianNumberEncodeHelper[7] := Line[{{\[Delta], 1 - \[Delta]}, {\[Delta], 1}, {0, 1}}]
CistercianNumberEncodeHelper[8] := Line[{{0, 1 - \[Delta]}, {\[Delta], 1 - \[Delta]}, {\[Delta], 1}}]
CistercianNumberEncodeHelper[9] := Line[{{0, 1}, {\[Delta], 1}, {\[Delta], 1 - \[Delta]}, {0, 1 - \[Delta]}}]
CistercianNumberEncode::nnarg = "The argument `1` should be an integer between 0 and 9999 (inclusive).";
CistercianNumberEncode[n_Integer] := Module[{digs},
  If[0 <= n <= 9999,
   digs = IntegerDigits[n, 10, 4];
   Graphics[{Line[{{0, 0}, {0, 1}}],
     CistercianNumberEncodeHelper[digs[[4]]],
     GeometricTransformation[CistercianNumberEncodeHelper[digs[[3]]], 
      ReflectionTransform[{1, 0}]],
     GeometricTransformation[CistercianNumberEncodeHelper[digs[[2]]], 
      ReflectionTransform[{0, 1}, {0, 1/2}]],
     GeometricTransformation[CistercianNumberEncodeHelper[digs[[1]]], 
      RotationTransform[Pi, {0, 1/2}]]
     },
    PlotRange -> {{-1.5 \[Delta], 1.5 \[Delta]}, {0 - 0.5 \[Delta], 
       1 + 0.5 \[Delta]}},
    ImageSize -> 50
    ]
   ,
   Message[CistercianNumberEncode::nnarg, n]
   ]
  ]
CistercianNumberEncode[0]
CistercianNumberEncode[1]
CistercianNumberEncode[20]
CistercianNumberEncode[300]
CistercianNumberEncode[4000]
CistercianNumberEncode[5555]
CistercianNumberEncode[6789]
CistercianNumberEncode[1337]
Output:

A set of Graphics is shown for each of the numerals.

Nim

Translation of: Kotlin
const Size = 15

type Canvas = array[Size, array[Size, char]]


func horizontal(canvas: var Canvas; col1, col2, row: Natural) =
  for col in col1..col2:
    canvas[row][col] = 'x'


func vertical(canvas: var Canvas; row1, row2, col: Natural) =
  for row in row1..row2:
    canvas[row][col] = 'x'


func diagd(canvas: var Canvas; col1, col2, row: Natural) =
  for col in col1..col2:
    canvas[row + col - col1][col] = 'x'


func diagu(canvas: var Canvas; col1, col2, row: Natural) =
  for col in col1..col2:
    canvas[row - col + col1][col] = 'x'


func drawPart(canvas: var Canvas; value: Natural) =

  case value
  of 1:
    canvas.horizontal(6, 10, 0)
  of 2:
    canvas.horizontal(6, 10, 4)
  of 3:
    canvas.diagd(6, 10, 0)
  of 4:
    canvas.diagu(6, 10, 4)
  of 5:
    canvas.drawPart(1)
    canvas.drawPart(4)
  of 6:
    canvas.vertical(0, 4, 10)
  of 7:
    canvas.drawPart(1)
    canvas.drawPart(6)
  of 8:
    canvas.drawPart(2)
    canvas.drawPart(6)
  of 9:
    canvas.drawPart(1)
    canvas.drawPart(8)
  of 10:
    canvas.horizontal(0, 4, 0)
  of 20:
    canvas.horizontal(0, 4, 4)
  of 30:
    canvas.diagu(0, 4, 4)
  of 40:
    canvas.diagd(0, 4, 0)
  of 50:
    canvas.drawPart(10)
    canvas.drawPart(40)
  of 60:
    canvas.vertical(0, 4, 0)
  of 70:
    canvas.drawPart(10)
    canvas.drawPart(60)
  of 80:
    canvas.drawPart(20)
    canvas.drawPart(60)
  of 90:
    canvas.drawPart(10)
    canvas.drawPart(80)
  of 100:
    canvas.horizontal(6, 10, 14)
  of 200:
    canvas.horizontal(6, 10, 10)
  of 300:
    canvas.diagu(6, 10, 14)
  of 400:
    canvas.diagd(6, 10, 10)
  of 500:
    canvas.drawPart(100)
    canvas.drawPart(400)
  of 600:
    canvas.vertical(10, 14, 10)
  of 700:
    canvas.drawPart(100)
    canvas.drawPart(600)
  of 800:
    canvas.drawPart(200)
    canvas.drawPart(600)
  of 900:
    canvas.drawPart(100)
    canvas.drawPart(800)
  of 1000:
    canvas.horizontal(0, 4, 14)
  of 2000:
    canvas.horizontal(0, 4, 10)
  of 3000:
    canvas.diagd(0, 4, 10)
  of 4000:
    canvas.diagu(0, 4, 14)
  of 5000:
    canvas.drawPart(1000)
    canvas.drawPart(4000)
  of 6000:
    canvas.vertical(10, 14, 0)
  of 7000:
    canvas.drawPart(1000)
    canvas.drawPart(6000)
  of 8000:
    canvas.drawPart(2000)
    canvas.drawPart(6000)
  of 9000:
    canvas.drawPart(1000)
    canvas.drawPart(8000)
  else:
    raise newException(ValueError, "wrong value for 'drawPart'")


func draw(canvas: var Canvas; value: Natural) =

  var val = value
  let thousands = val div 1000
  val = val mod 1000
  let hundreds = val div 100
  val = val mod 100
  let tens = val div 10
  let ones = val mod 10

  if thousands != 0:
    canvas.drawPart(1000 * thousands)
  if hundreds != 0:
    canvas.drawPart(100 * hundreds)
  if tens != 0:
    canvas.drawPart(10 * tens)
  if ones != 0:
    canvas.drawPart(ones)


func cistercian(n: Natural): Canvas =
  for row in result.mitems:
    for cell in row.mitems: cell = ' '
    row[5] = 'x'
  result.draw(n)


proc `$`(canvas: Canvas): string =
  for row in canvas:
    for cell in row:
      result.add cell
    result.add '\n'


when isMainModule:

  for number in [0, 1, 20, 300, 4000, 5555, 6789, 9999]:
    echo number, ':'
    echo cistercian(number)
Output:
0:
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         

1:
     xxxxxx    
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         

20:
     x         
     x         
     x         
     x         
xxxxxx         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         

300:
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x    x    
     x   x     
     x  x      
     x x       
     xx        

4000:
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
     x         
    xx         
   x x         
  x  x         
 x   x         
x    x         

5555:
xxxxxxxxxxx    
 x   x   x     
  x  x  x      
   x x x       
    xxx        
     x         
     x         
     x         
     x         
     x         
    xxx        
   x x x       
  x  x  x      
 x   x   x     
xxxxxxxxxxx    

6789:
x    xxxxxx    
x    x    x    
x    x    x    
x    x    x    
xxxxxxxxxxx    
     x         
     x         
     x         
     x         
     x         
x    x    x    
x    x    x    
x    x    x    
x    x    x    
x    xxxxxx    

9999:
xxxxxxxxxxx    
x    x    x    
x    x    x    
x    x    x    
xxxxxxxxxxx    
     x         
     x         
     x         
     x         
     x         
xxxxxxxxxxx    
x    x    x    
x    x    x    
x    x    x    
xxxxxxxxxxx    

Perl

#!/usr/bin/perl

use strict; # https://rosettacode.org/wiki/Cistercian_numerals
use warnings;

my @pts = ('', qw( 01 23 03 12 012 13 013 132 0132) );
my @dots = qw( 4-0 8-0 4-4 8-4 );

my @images = map { sprintf("%-9s\n", "$_:") . draw($_) }
  0, 1, 20, 300, 4000, 5555, 6789, 1133;
for ( 1 .. 13 )
  {
  s/(.+)\n/ print " $1"; '' /e for @images;
  print "\n";
  }

sub draw
  {
  my $n = shift;
  local $_ = "    #    \n" x 12;
  my $quadrant = 0;
  for my $digit ( reverse split //, sprintf "%04d", $n )
    {
    my ($oldx, $oldy);
    for my $cell ( split //, $pts[$digit] )
      {
      my ($x, $y) = split /-/, $dots[$cell];
      if( defined $oldx )
        {
        my $dirx = $x <=> $oldx;
        my $diry = $y <=> $oldy;
        for my $place ( 0 .. 3 )
          {
          substr $_, $oldx + $oldy * 10, 1, '#';
          $oldx += $dirx;
          $oldy += $diry;
          }
        }
      ($oldx, $oldy) = ($x, $y);
      }
    s/.+/ reverse $& /ge;
    ++$quadrant & 1 or $_ = join '', reverse /.+\n/g;
    }
  return $_;
  }
Output:
 0:        1:        20:       300:      4000:     5555:     6789:     1133:    
     #         ####      #         #         #     ######### #   #####     #    
     #         #         #         #         #      #  #  #  #   #   #    ###   
     #         #         #         #         #       # # #   #   #   #   # # #  
     #         #         #         #         #        ###    #   #   #  #  #  # 
     #         #      ####         #         #         #     #########     #    
     #         #         #         #         #         #         #         #    
     #         #         #         #         #         #         #         #    
     #         #         #         #         #         #         #         #    
     #         #         #         #  #     ##        ###    #   #   #     #    
     #         #         #         # #     # #       # # #   #   #   #     #    
     #         #         #         ##     #  #      #  #  #  #   #   #     #    
     #         #         #         #     #   #     ######### #   #####  ####### 

Phix

--
-- Define each digit as {up-down multiplier, left-right multiplier, char},
--              that is starting each drawing from line 1 or 7, column 3,
--              and with `/` and `\` being flipped below when necessary.
--
with javascript_semantics
constant ds = {{{0,0,'+'},{0,1,'-'},{0,2,'-'}},     -- 1
               {{2,0,'+'},{2,1,'-'},{2,2,'-'}},     -- 2
               {{0,0,'+'},{1,1,'\\'},{2,2,'\\'}},   -- 3
               {{2,0,'+'},{1,1,'/'},{0,2,'/'}},     -- 4
               {{2,0,'+'},{1,1,'/'},{0,2,'+'},
                          {0,0,'+'},{0,1,'-'}},     -- 5
               {{0,2,'|'},{1,2,'|'},{2,2,'|'}},     -- 6
               {{0,0,'+'},{0,1,'-'},{0,2,'+'},
                          {1,2,'|'},{2,2,'|'}},     -- 7
               {{2,0,'+'},{2,1,'-'},{2,2,'+'},
                          {1,2,'|'},{0,2,'|'}},     -- 8
               {{2,0,'+'},{2,1,'-'},{2,2,'+'},
                          {1,2,'|'},{0,2,'+'},
                          {0,1,'-'},{0,0,'+'}}}     -- 9
 
function cdigit(sequence s, integer d, pos)
--
-- s is our canvas, 7 lines of 5 characters
-- d is the digit, 0..9
-- pos is 4..1 for bl,br,tl,tr (easier to say/see 'backwards')
--
    if d then
        integer ud = {+1,+1,-1,-1}[pos],
                lr = {+1,-1,+1,-1}[pos],
                l = {1,1,7,7}[pos]
        sequence dset = ds[d]
        for i=1 to length(dset) do
            integer {udm, lrm, ch} = dset[i],
                    tf = find(ch,`/\`)
            if tf and ud!=lr then ch=`\/`[tf] end if
            s[l+ud*udm][3+lr*lrm] = ch
        end for
    end if
    return s
end function
 
procedure cisterian(sequence n)
    sequence res = {}
    for i=1 to length(n) do
        integer cn = n[i]
        res = append(res,sprintf("%4d:",cn))
        sequence s = repeat("  |  ",7)
        integer pos = 1
        while cn do
            s = cdigit(s, remainder(cn,10), pos)
            pos += 1
            cn = floor(cn/10)
        end while
        res &= s
    end for
    puts(1,join_by(res,8,10))
end procedure
 
cisterian({0,1,2,3,4,5,6,7,8,9,20, 300, 4000, 5555, 6789, 9394, 7922, 9999})
Output:
   0:      1:      2:      3:      4:      5:      6:      7:      8:      9:
  |       +--     |       +       | /     +-+     | |     +-+     | |     +-+
  |       |       |       |\      |/      |/      | |     | |     | |     | |
  |       |       +--     | \     +       +       | |     | |     +-+     +-+
  |       |       |       |       |       |       |       |       |       |
  |       |       |       |       |       |       |       |       |       |
  |       |       |       |       |       |       |       |       |       |
  |       |       |       |       |       |       |       |       |       |

  20:    300:   4000:   5555:   6789:   9394:   7922:   9999:
  |       |       |     +-+-+   | +-+   +-+ /     |     +-+-+
  |       |       |      \|/    | | |   | |/      |     | | |
--+       |       |       +     +-+-+   +-+     --+--   +-+-+
  |       |       |       |       |       |       |       |
  |       | /     +       +     | | |   +-+ /   | +-+   +-+-+
  |       |/     /|      /|\    | | |   | |/    | | |   | | |
  |       +     / |     +-+-+   | +-+   +-+     +-+-+   +-+-+

Plain English

To run:
Start up.
Show some example Cistercian numbers.
Wait for the escape key.
Shut down.

To show some example Cistercian numbers:
Put the screen's left plus 1 inch into the context's spot's x.
Clear the screen to the lightest gray color.
Use the black color.
Use the fat pen.
Draw 0.
Draw 1.
Draw 20.
Draw 300.
Draw 4000.
Draw 5555.
Draw 6789.
Draw 9394.
Refresh the screen.

The mirror flag is a flag.

To draw a Cistercian number:
Split the Cistercian number into some thousands and some hundreds and some tens and some ones.
Stroke zero.
Set the mirror flag.
Stroke the ones.
Clear the mirror flag.
Stroke the tens.
Turn around.
Stroke the hundreds.
Set the mirror flag.
Stroke the thousands.
Turn around.
Label the Cistercian number.
Move the context's spot right 1 inch.

To label a Cistercian number:
Save the context.
Move down the half stem plus the small stem.
Imagine a box with the context's spot and the context's spot.
Draw "" then the Cistercian number in the center of the box with the dark gray color.
Restore the context.

Some tens are a number.

Some ones are a number.

To split a number into some thousands and some hundreds and some tens and some ones:
Divide the number by 10 giving a quotient and a remainder.
Put the remainder into the ones.
Divide the quotient by 10 giving another quotient and another remainder.
Put the other remainder into the tens.
Divide the other quotient by 10 giving a third quotient and a third remainder.
Put the third remainder into the hundreds.
Divide the third quotient by 10 giving a fourth quotient and a fourth remainder.
Put the fourth remainder into the thousands.

The small stem is a length equal to 1/6 inch.

The half stem is a length equal to 1/2 inch.

The tail is a length equal to 1/3 inch.

The slanted tail is a length equal to 6/13 inch.

To stroke a number:
Save the context.
If the number is 1, stroke one.
If the number is 2, stroke two.
If the number is 3, stroke three.
If the number is 4, stroke four.
If the number is 5, stroke five.
If the number is 6, stroke six.
If the number is 7, stroke seven.
If the number is 8, stroke eight.
If the number is 9, stroke nine.
Restore the context.

To turn home:
If the mirror flag is set, turn right; exit.
Turn left.

To turn home some fraction of the way:
If the mirror flag is set, turn right the fraction; exit.
Turn left the fraction.

To stroke zero:
Save the context.
Stroke the half stem.
Turn around.
Move the half stem.
Stroke the half stem.
Restore the context.

To stroke one:
Move the half stem.
Turn home.
Stroke the tail.

To stroke two:
Move the small stem.
Turn home.
Stroke the tail.

To stroke three:
Move the half stem.
Turn home 3/8 of the way.
Stroke the slanted tail.

To stroke four:
Move the small stem.
Turn home 1/8 of the way.
Stroke the slanted tail.

To stroke five:
Stroke 1.
Stroke 4.

To stroke six:
Move the half stem.
Turn home.
Move the tail.
Turn home.
Stroke the tail.

To stroke seven:
Stroke 1.
Stroke 6.

To stroke eight:
Stroke 2.
Stroke 6.

To stroke nine:
Stroke 1.
Stroke 8.
Output:

https://commons.wikimedia.org/wiki/File:Cistercian_numerals.png

Python

I tried to create a three-line font from UTF8 characters taking three lines per Cistercian number.

# -*- coding: utf-8 -*-
"""
Some UTF-8 chars used:
    
‾	8254	203E	&oline;	OVERLINE
┃	9475	2503	 	BOX DRAWINGS HEAVY VERTICAL
╱	9585	2571	 	BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
╲	9586	2572	 	BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
◸	9720	25F8	 	UPPER LEFT TRIANGLE
◹	9721	25F9	 	UPPER RIGHT TRIANGLE
◺	9722	25FA	 	LOWER LEFT TRIANGLE
◻	9723	25FB	 	WHITE MEDIUM SQUARE
◿	9727	25FF	 	LOWER RIGHT TRIANGLE

"""

#%% digit sections

def _init():
    "digit sections for forming numbers"
    digi_bits = """
#0  1   2  3  4  5  6   7   8   9
#
 .  ‾   _  ╲  ╱  ◸  .|  ‾|  _|  ◻
#
 .  ‾   _  ╱  ╲  ◹  |.  |‾  |_  ◻
#
 .  _  ‾   ╱  ╲  ◺  .|  _|  ‾|  ◻
#
 .  _  ‾   ╲  ╱  ◿  |.  |_  |‾  ◻
 
""".strip()

    lines = [[d.replace('.', ' ') for d in ln.strip().split()]
             for ln in digi_bits.strip().split('\n')
             if '#' not in ln]
    formats = '<2 >2 <2 >2'.split()
    digits = [[f"{dig:{f}}" for dig in line]
              for f, line in zip(formats, lines)]

    return digits

_digits = _init()


#%% int to 3-line strings
def _to_digits(n):
    assert 0 <= n < 10_000 and int(n) == n
    
    return [int(digit) for digit in f"{int(n):04}"][::-1]

def num_to_lines(n):
    global _digits
    d = _to_digits(n)
    lines = [
        ''.join((_digits[1][d[1]], '┃',  _digits[0][d[0]])),
        ''.join((_digits[0][   0], '┃',  _digits[0][   0])),
        ''.join((_digits[3][d[3]], '┃',  _digits[2][d[2]])),
        ]
    
    return lines

def cjoin(c1, c2, spaces='   '):
    return [spaces.join(by_row) for by_row in zip(c1, c2)]

#%% main
if __name__ == '__main__':
    #n = 6666
    #print(f"Arabic {n} to Cistercian:\n")
    #print('\n'.join(num_to_lines(n)))
    
    for pow10 in range(4):    
        step = 10 ** pow10
        print(f'\nArabic {step}-to-{9*step} by {step} in Cistercian:\n')
        lines = num_to_lines(step)
        for n in range(step*2, step*10, step):
            lines = cjoin(lines, num_to_lines(n))
        print('\n'.join(lines))
    

    numbers = [0, 5555, 6789, 6666]
    print(f'\nArabic {str(numbers)[1:-1]} in Cistercian:\n')
    lines = num_to_lines(numbers[0])
    for n in numbers[1:]:
        lines = cjoin(lines, num_to_lines(n))
    print('\n'.join(lines))
Output:
Arabic 1-to-9 by 1 in Cistercian:

  ┃‾      ┃_      ┃╲      ┃╱      ┃◸      ┃ |     ┃‾|     ┃_|     ┃◻ 
  ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃  
  ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃  

Arabic 10-to-90 by 10 in Cistercian:

 ‾┃      _┃      ╱┃      ╲┃      ◹┃     | ┃     |‾┃     |_┃      ◻┃  
  ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃  
  ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃  

Arabic 100-to-900 by 100 in Cistercian:

  ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃  
  ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃  
  ┃_      ┃‾      ┃╱      ┃╲      ┃◺      ┃ |     ┃_|     ┃‾|     ┃◻ 

Arabic 1000-to-9000 by 1000 in Cistercian:

  ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃  
  ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃       ┃  
 _┃      ‾┃      ╲┃      ╱┃      ◿┃     | ┃     |_┃     |‾┃      ◻┃  

Arabic 0, 5555, 6789, 6666 in Cistercian:

  ┃      ◹┃◸    |_┃◻    | ┃ |
  ┃       ┃       ┃       ┃  
  ┃      ◿┃◺    | ┃_|   | ┃ |

Note: There may be some horizontal placement issues evident in the HTML rendering between pre tags that may not be shown in the monospace rendering of a terminal (or the edit pane in firefox).
The pre tag may have to shift from one monospace font to a second that contains a character missing from the first. Those two individually monospaced fonts may have differing character widths between fonts (although consistent within individual monospaced fonts).
Paste the output into a monospace code editor and the stems of each number might well align!

Quackery

  [ $ "turtleduck.qky" loadfile ] now!

  [ [ 50 dup * 2 * 1
    10 vsqrt drop
    join ] constant
    do ]                 is diag       (   --> n/d )

  [ stack 1 ]            is side       (   --> s   )

  [ 0 side take
    - side put ]         is otherside  (   -->     )

  [ 150 1 walk
    -150 1 fly ]         is trunk      (   -->     )

  [ 50 1 fly ]           is inset      (   -->     )

  [ -50 1 fly ]          is outset     (   -->     )

  [ 150 1 fly
    1 2 turn ]           is otherend   (   -->     )

  [ ]                    is zero       (   -->     )

  [ -1 4 turn
    50 side share *
    dup 1 walk
    negate 1 fly
    1 4 turn ]           is one        (   -->     )

  [ inset one outset ]   is two        (   -->     )

  [ -1 side share *
    8 turn
    diag walk
    diag -v fly
    1 side share *
    8 turn ]             is three      (   -->     )

  [ inset
    -3 side share *
    8 turn
    diag walk
    diag -v fly
    3 side share *
    8 turn
    outset ]             is four       (   -->     )

  [ one four ]           is five       (   -->     )

  [ 1 side share *
    4 turn outset
    one
    inset
    -1 side share *
    4 turn ]             is six        (   -->     )

  [ one six ]            is seven      (   -->     )

  [ two six ]            is eight      (   -->     )

  [ one two six ]        is nine       (   -->     )

  [ [ table
      zero one two
      three four five
      six seven eight
      nine ] do ]        is thousands  ( n -->     )

  [ otherend
    thousands
    otherend ]           is units      ( n -->     )

  [ otherside
    units
    otherside ]          is tens       ( n -->     )

  [ otherside
    thousands
    otherside ]          is hundreds   ( n -->     )

  [ inset
    -1 4 turn
    trunk
    ' [ units tens
        hundreds
        thousands ]
    witheach
      [ dip
          [ 10 /mod ]
        do ]
    drop
    1 4 turn
    outset ]             is cistercian ( n -->     )

   [ dup witheach
       [ cistercian
         3 times inset ]
     size 3 * times
       outset ]          is task       ( [ -->     )


  turtle 5 wide -600 1 fly
 ' [ 0 1 20 300 4000 5555 6789 1234 ] task
Output:

Raku

Handles 0 through 9999 only. No error trapping. If you feed it an unsupported number it will truncate to maximum 4 digits.

my @line-segments = (0, 0, 0, 100),
    (0,  0, 35,  0), (0, 35, 35, 35), (0,  0, 35, 35), (0, 35, 35,  0), ( 35,  0, 35, 35),
    (0,  0,-35,  0), (0, 35,-35, 35), (0,  0,-35, 35), (0, 35,-35,  0), (-35,  0,-35, 35),
    (0,100, 35,100), (0, 65, 35, 65), (0,100, 35, 65), (0, 65, 35,100), ( 35, 65, 35,100),
    (0,100,-35,100), (0, 65,-35, 65), (0,100,-35, 65), (0, 65,-35,100), (-35, 65,-35,100);

my @components = map {@line-segments[$_]}, |((0, 5, 10, 15).map: -> $m {
    |((0,), (1,), (2,), (3,), (4,), (1,4), (5,), (1,5), (2,5), (1,2,5)).map: {$_ »+» $m}
});

my $out = 'Cistercian-raku.svg'.IO.open(:w);

$out.say: # insert header
q|<svg  width="875" height="470" style="stroke:black;" version="1.1" xmlns="http://www.w3.org/2000/svg">
 <rect width="100%" height="100%" style="fill:white;"/>|;

my $hs = 50; # horizontal spacing
my $vs = 25; # vertical spacing

for flat ^10, 20, 300, 4000, 5555, 6789, 9394, (^10000).pick(14) -> $cistercian {

    $out.say: |@components[0].map: { # draw zero / base vertical bar
        qq|<line x1="{.[0] + $hs}" y1="{.[1] + $vs}" x2="{.[2] + $hs}" y2="{.[3] + $vs}"/>|
    };

    my @orders-of-magnitude = $cistercian.polymod(10 xx *);

    for @orders-of-magnitude.kv -> $order, $value {
        next unless $value; # skip zeros, already drew zero bar
        last if $order > 3; # truncate too large integers

        # draw the component line segments
        $out.say: join "\n", @components[$order * 10 + $value].map: {
            qq|<line x1="{.[0] + $hs}" y1="{.[1] + $vs}" x2="{.[2] + $hs}" y2="{.[3] + $vs}"/>|
        }
    }

    # insert the decimal number below
    $out.say: qq|<text x="{$hs - 5}" y="{$vs + 120}">{$cistercian}</text>|;

    if ++$ %% 10 { # next row
        $hs = -35;
        $vs += 150;
    }

    $hs += 85; # increment horizontal spacing


}
$out.say: q|</svg>|; # insert footer
Output:

REXX

A fair amount of code dealt with displaying multiple Cistercian numerals on the terminal,   and also trying to present
ASCII characters that tried mimicking what a scribe might draw.

Comprehensive error checking was also included.

/*REXX program displays a (non-negative 4-digit) integer in  Cistercian (monk) numerals.*/
parse arg m                                      /*obtain optional arguments from the CL*/
if m='' | m=","  then m= 0 1 20 300 4000 5555 6789 9393  /*Not specified?  Use defaults.*/
$.=;                     nnn= words(m)
             do j=1  for nnn;   z= word(m, j)            /*process each of the numbers. */
             if \datatype(z, 'W')  then call serr  "number isn't numeric: "           z
             if \datatype(z, 'N')  then call serr  "number isn't an integer: "        z
             z= z / 1                            /*normalize the number:  006  5.0  +4  */
             if z<0                then call serr  "number can't be negative: "       z
             if z>9999             then call serr  "number is too large (>9,999): "   z
             call monk z / 1                     /*create the Cistercian quad numeral.  */
             end   /*j*/
call show                                        /*display   "      "       "     "     */
exit 0                                           /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
@:    parse arg @x,@y;  return @.@x.@y           /*return a value from the point (@x,@y)*/
quad: parse arg #;   if #\==0  then interpret 'call' #;  return       /*build a numeral.*/
serr: say '***error*** '  arg(1);    exit 13                          /*issue error msg.*/
app:   do r= 9 for 10 by -1; do c=-5 for 11; $.r= $.r||@.c.r; end; $.r=$.r b5; end; return
eye:   do a=0  for 10; @.0.a= '│';   end; return /*build an "eye" glyph (vertical axis).*/
p:     do k=1  by 3  until k>arg(); x= arg(k); y= arg(k+1); @.x.y= arg(k+2); end;   return
sect:  do q=1  for 4; call quad s.q; end; return /*build a Cistercian numeral character.*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
monk: parse arg n; n= right(n, 4, 0);  @.= ' '   /*zero─fill N;  blank─out numeral grid.*/
      b4= left('', 4);  b5= b4" ";   $.11= $.11  ||  b4  ||  n  ||  b4  ||  b5;   call eye
      parse var n s.4 2 s.3 3 s.2 4 s.1;    call sect;    call nice;    call app;   return
/*──────────────────────────────────────────────────────────────────────────────────────*/
nice: if @(-1, 9)=='─'     then call p 0, 9, "┐";    if @(1,9)=='─'  then call p 0, 9, "┌"
      if @(-1, 9)=='─'  &  @(1,9)=='─'                               then call p 0, 9, "┬"
      if @(-1, 0)=='─'     then call p 0, 0, "┘";    if @(1,0)=='─'  then call p 0, 0, "└"
      if @(-1, 0)=='─'  &  @(1,0)=='─'                               then call p 0, 0, "┴"
         do i=4  to 5
         if @(-1, i)=='─'  then call p 0, i, "┤";    if @(1,i)=='─'  then call p 0, i, "├"
         if @(-1, i)=='─'  &  @(1,i)=="─"                            then call p 0, i, "┼"
         end   /*i*/;                                                               return
/*──────────────────────────────────────────────────────────────────────────────────────*/
show:    do jj= 11  for 10+2  by -1;    say strip($.jj, 'T')  /*display 1 row at a time.*/
         if jj==5  then do 3;           say strip( copies(b5'│'b5 b5, nnn), 'T');     end
         end   /*r*/;                   return
/*──────────────────────────────────────────────────────────────────────────────────────*/
1: ?= '─';  if q==1  then call p  1, 9, ?,  2, 9, ?,  3, 9, ?,  4, 9, ?,  5, 9, ?
            if q==2  then call p -1, 9, ?, -2, 9, ?, -3, 9, ?, -4, 9, ?, -5, 9, ?
            if q==3  then call p  1, 0, ?,  2, 0, ?,  3, 0, ?,  4, 0, ?,  5, 0, ?
            if q==4  then call p -1, 0, ?, -2, 0, ?, -3, 0, ?, -4, 0, ?, -5, 0, ?;  return
/*──────────────────────────────────────────────────────────────────────────────────────*/
2: ?= '─';  if q==1  then call p  1, 5, ?,  2, 5, ?,  3, 5, ?,  4, 5, ?,  5, 5, ?
            if q==2  then call p -1, 5, ?, -2, 5, ?, -3, 5, ?, -4, 5, ?, -5, 5, ?
            if q==3  then call p  1, 4, ?,  2, 4, ?,  3, 4, ?,  4, 4, ?,  5, 4, ?
            if q==4  then call p -1, 4, ?, -2, 4, ?, -3, 4, ?, -4, 4, ?, -5, 4, ?;  return
/*──────────────────────────────────────────────────────────────────────────────────────*/
3: ?= '\';  if q==1  then call p  1, 9, ?,  2, 8, ?,  3, 7, ?,  4, 6, ?,  5, 5, ?
   ?= '/';  if q==2  then call p -1, 9, ?, -2, 8, ?, -3, 7, ?, -4, 6, ?, -5, 5, ?
   ?= '/';  if q==3  then call p  1, 0, ?,  2, 1, ?,  3, 2, ?,  4, 3, ?,  5, 4, ?
   ?= '\';  if q==4  then call p -5, 4, ?, -4, 3, ?, -3, 2, ?, -2, 1, ?, -1, 0, ?;  return
/*──────────────────────────────────────────────────────────────────────────────────────*/
4: ?= '/';  if q==1  then call p  1, 5, ?,  2, 6, ?,  3, 7, ?,  4, 8, ?,  5, 9, ?
   ?= '\';  if q==2  then call p -5, 9, ?, -4, 8, ?, -3, 7, ?, -2, 6, ?, -1, 5, ?
   ?= '\';  if q==3  then call p  1, 4, ?,  2, 3, ?,  3, 2, ?,  4, 1, ?,  5, 0, ?
   ?= '/';  if q==4  then call p -5, 0, ?, -4, 1, ?, -3, 2, ?, -2, 3, ?, -1, 4, ?;  return
/*──────────────────────────────────────────────────────────────────────────────────────*/
5: ?= '/';  if q==1  then call p  1, 5, ?,  2, 6, ?,  3, 7, ?,  4, 8, ?
   ?= '\';  if q==2  then call p -4, 8, ?, -3, 7, ?, -2, 6, ?, -1, 5, ?
   ?= '\';  if q==3  then call p  1, 4, ?,  2, 3, ?,  3, 2, ?,  4, 1, ?
   ?= '/';  if q==4  then call p -4, 1, ?, -3, 2, ?, -2, 3, ?, -1, 4, ?;  call 1;   return
/*──────────────────────────────────────────────────────────────────────────────────────*/
6: ?= '│';  if q==1  then call p  5, 9, ?,  5, 8, ?,  5, 7, ?,  5, 6, ?,  5, 5, ?
            if q==2  then call p -5, 9, ?, -5, 8, ?, -5, 7, ?, -5, 6, ?, -5, 5, ?
            if q==3  then call p  5, 0, ?,  5, 1, ?,  5, 2, ?,  5, 3, ?,  5, 4, ?
            if q==4  then call p -5, 0, ?, -5, 1, ?, -5, 2, ?, -5, 3, ?, -5, 4, ?;  return
/*──────────────────────────────────────────────────────────────────────────────────────*/
7:          call 1;  call 6;         if q==1  then call p  5, 9, '┐'
                                     if q==2  then call p -5, 9, '┌'
                                     if q==3  then call p  5, 0, '┘'
                                     if q==4  then call p -5, 0, '└';               return
/*──────────────────────────────────────────────────────────────────────────────────────*/
8:          call 2;  call 6;         if q==1  then call p  5, 5, '┘'
                                     if q==2  then call p -5, 5, '└'
                                     if q==3  then call p  5, 4, '┐'
                                     if q==4  then call p -5, 4, '┌';               return
/*──────────────────────────────────────────────────────────────────────────────────────*/
9:          call 1; call 2; call 6;  if q==1  then call p  5, 5, '┘',  5, 9, "┐"
                                     if q==2  then call p -5, 5, '└', -5, 9, "┌"
                                     if q==3  then call p  5, 0, '┘',  5, 4, "┐"
                                     if q==4  then call p -5, 0, '└', -5, 4, "┌";   return
output   when using the default inputs:

(Shown at three-quarter size.)

    0000             0001             0020             0300             4000             5555             6789             9393

     │                ┌─────           │                │                │           ─────┬─────      │    ┌────┐      ┌────┐\
     │                │                │                │                │            \   │   /       │    │    │      │    │ \
     │                │                │                │                │             \  │  /        │    │    │      │    │  \
     │                │                │                │                │              \ │ /         │    │    │      │    │   \
     │                │           ─────┤                │                │               \│/          └────┼────┘      └────┤    \
     │                │                │                │                │                │                │                │
     │                │                │                │                │                │                │                │
     │                │                │                │                │                │                │                │
     │                │                │                │    /          /│               /│\          │    │    │      ┌────┤    /
     │                │                │                │   /          / │              / │ \         │    │    │      │    │   /
     │                │                │                │  /          /  │             /  │  \        │    │    │      │    │  /
     │                │                │                │ /          /   │            /   │   \       │    │    │      │    │ /
     │                │                │                │/          /    │           ─────┴─────      │    └────┘      └────┘/

Ruby

Translation of: Lua
def initN
    n = Array.new(15){Array.new(11, ' ')}
    for i in 1..15
        n[i - 1][5] = 'x'
    end
    return n
end

def horiz(n, c1, c2, r)
    for c in c1..c2
        n[r][c] = 'x'
    end
end

def verti(n, r1, r2, c)
    for r in r1..r2
        n[r][c] = 'x'
    end
end

def diagd(n, c1, c2, r)
    for c in c1..c2
        n[r+c-c1][c] = 'x'
    end
end

def diagu(n, c1, c2, r)
    for c in c1..c2
        n[r-c+c1][c] = 'x'
    end
end

def initDraw
    draw = []

    draw[1] = lambda do |n| horiz(n, 6, 10, 0) end
    draw[2] = lambda do |n| horiz(n, 6, 10, 4) end
    draw[3] = lambda do |n| diagd(n, 6, 10, 0) end
    draw[4] = lambda do |n| diagu(n, 6, 10, 4) end
    draw[5] = lambda do |n|
        draw[1].call(n)
        draw[4].call(n)
    end
    draw[6] = lambda do |n| verti(n, 0, 4, 10) end
    draw[7] = lambda do |n|
        draw[1].call(n)
        draw[6].call(n)
    end
    draw[8] = lambda do |n|
        draw[2].call(n)
        draw[6].call(n)
    end
    draw[9] = lambda do |n|
        draw[1].call(n)
        draw[8].call(n)
    end

    draw[10] = lambda do |n| horiz(n, 0, 4, 0) end
    draw[20] = lambda do |n| horiz(n, 0, 4, 4) end
    draw[30] = lambda do |n| diagu(n, 0, 4, 4) end
    draw[40] = lambda do |n| diagd(n, 0, 4, 0) end
    draw[50] = lambda do |n|
        draw[10].call(n)
        draw[40].call(n)
    end
    draw[60] = lambda do |n| verti(n, 0, 4, 0) end
    draw[70] = lambda do |n|
        draw[10].call(n)
        draw[60].call(n)
    end
    draw[80] = lambda do |n|
        draw[20].call(n)
        draw[60].call(n)
    end
    draw[90] = lambda do |n|
        draw[10].call(n)
        draw[80].call(n)
    end

    draw[100] = lambda do |n| horiz(n, 6, 10, 14) end
    draw[200] = lambda do |n| horiz(n, 6, 10, 10) end
    draw[300] = lambda do |n| diagu(n, 6, 10, 14) end
    draw[400] = lambda do |n| diagd(n, 6, 10, 10) end
    draw[500] = lambda do |n|
        draw[100].call(n)
        draw[400].call(n)
    end
    draw[600] = lambda do |n| verti(n, 10, 14, 10) end
    draw[700] = lambda do |n|
        draw[100].call(n)
        draw[600].call(n)
    end
    draw[800] = lambda do |n|
        draw[200].call(n)
        draw[600].call(n)
    end
    draw[900] = lambda do |n|
        draw[100].call(n)
        draw[800].call(n)
    end

    draw[1000] = lambda do |n| horiz(n, 0, 4, 14) end
    draw[2000] = lambda do |n| horiz(n, 0, 4, 10) end
    draw[3000] = lambda do |n| diagd(n, 0, 4, 10) end
    draw[4000] = lambda do |n| diagu(n, 0, 4, 14) end
    draw[5000] = lambda do |n|
        draw[1000].call(n)
        draw[4000].call(n)
    end
    draw[6000] = lambda do |n| verti(n, 10, 14, 0) end
    draw[7000] = lambda do |n|
        draw[1000].call(n)
        draw[6000].call(n)
    end
    draw[8000] = lambda do |n|
        draw[2000].call(n)
        draw[6000].call(n)
    end
    draw[9000] = lambda do |n|
        draw[1000].call(n)
        draw[8000].call(n)
    end

    return draw
end

def printNumeral(n)
    for a in n
        for b in a
            print b
        end
        print "\n"
    end
    print "\n"
end

draw = initDraw()
for number in [0, 1, 20, 300, 4000, 5555, 6789, 9999]
    n = initN()
    print number, ":\n"

    thousands = (number / 1000).floor
    number = number % 1000

    hundreds = (number / 100).floor
    number = number % 100

    tens = (number / 10).floor
    ones = number % 10

    if thousands > 0 then
        draw[thousands * 1000].call(n)
    end
    if hundreds > 0 then
        draw[hundreds * 100].call(n)
    end
    if tens > 0 then
        draw[tens * 10].call(n)
    end
    if ones > 0 then
        draw[ones].call(n)
    end
    printNumeral(n)
end
Output:
0:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

1:
     xxxxxx
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

20:
     x
     x
     x
     x
xxxxxx
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x

300:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x    x
     x   x
     x  x
     x x
     xx

4000:
     x
     x
     x
     x
     x
     x
     x
     x
     x
     x
    xx
   x x
  x  x
 x   x
x    x

5555:
xxxxxxxxxxx
 x   x   x
  x  x  x
   x x x
    xxx
     x
     x
     x
     x
     x
    xxx
   x x x
  x  x  x
 x   x   x
xxxxxxxxxxx

6789:
x    xxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x
     x
     x
     x
     x
x    x    x
x    x    x
x    x    x
x    x    x
x    xxxxxx

9999:
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx
     x
     x
     x
     x
     x
xxxxxxxxxxx
x    x    x
x    x    x
x    x    x
xxxxxxxxxxx

Rust

Translation of: C
use once_cell::sync::Lazy;

const GRID_SIZE: usize = 15;
static mut CANVAS: Lazy<Vec<[char; GRID_SIZE]>> = Lazy::new(|| vec![[' '; GRID_SIZE]; GRID_SIZE],);

/// initialize CANVAS
fn init_n() {
    for i in 0..GRID_SIZE {
        for j in 0..GRID_SIZE {
            unsafe { CANVAS[i][j] = ' '; }
        }
        unsafe { CANVAS[i][5] = '#'; }
    }
}

/// draw horizontal
fn horizontal(c1: usize, c2: usize, r: usize) {
    for c in c1..=c2 {
        unsafe { CANVAS[r][c] = '#'; }
    }
}

/// draw vertical
fn vertical(r1: usize, r2: usize, c: usize) {
    for r in r1..=r2 {
        unsafe { CANVAS[r][c] = '#'; }
    }
}

/// draw diagonal NE to SW
fn diag_d(c1 : usize, c2: usize, r: usize) {
    for c in c1..=c2 {
        unsafe { CANVAS[r + c - c1][c] = '#'; }
    }
}

/// draw diagonal SE to NW
fn diag_u(c1: usize, c2: usize, r: usize) {
    for c in c1..=c2 {
        unsafe { CANVAS[r + c1 - c][c] = '#'; }
    }
}

/// Mark the portions of the ones place.
fn draw_ones(v: i32) {
    match v {
        1 => horizontal(6, 10, 0),
        2 => horizontal(6, 10, 4),
        3 => diag_d(6, 10, 0),
        4 => diag_u(6, 10, 4),
        5 => { draw_ones(1); draw_ones(4); },
        6 => vertical(0, 4, 10),
        7 => { draw_ones(1); draw_ones(6); },
        8 => { draw_ones(2); draw_ones(6); },
        9 => { draw_ones(1); draw_ones(8); },
        _ => {},
    }
}

/// Mark the portions of the tens place.
fn draw_tens(v: i32) {
    match v {
        1 => horizontal(0, 4, 0),
        2 => horizontal(0, 4, 4),
        3 => diag_u(0, 4, 4),
        4 => diag_d(0, 4, 0),
        5 => { draw_tens(1); draw_tens(4); },
        6 => vertical(0, 4, 0),
        7 => { draw_tens(1); draw_tens(6); },
        8 => { draw_tens(2); draw_tens(6); },
        9 => { draw_tens(1); draw_tens(8); },
        _ => {},
    }
}

/// Mark the portions of the hundreds place.
fn draw_hundreds(hundreds: i32) {
    match hundreds {
        1 => horizontal(6, 10, 14),
        2 => horizontal(6, 10, 10),
        3 => diag_u(6, 10, 14),
        4 => diag_d(6, 10, 10),
        5 => { draw_hundreds(1); draw_hundreds(4) },
        6 => vertical(10, 14, 10),
        7 => { draw_hundreds(1); draw_hundreds(6); },
        8 => { draw_hundreds(2); draw_hundreds(6); },
        9 => { draw_hundreds(1); draw_hundreds(8); },
        _ => {},
    }
}

/// Mark the portions of the thousands place.
fn draw_thousands(thousands: i32) {
    match thousands {
        1 => horizontal(0, 4, 14),
        2 => horizontal(0, 4, 10),
        3 => diag_d(0, 4, 10),
        4 => diag_u(0, 4, 14),
        5 => { draw_thousands(1); draw_thousands(4); },
        6 => vertical(10, 14, 0),
        7 => { draw_thousands(1); draw_thousands(6); },
        8 => { draw_thousands(2); draw_thousands(6); },
        9 => { draw_thousands(1); draw_thousands(8); },
        _ => {},
    }
}

/// Mark the char matrix for the numeral drawing.
fn draw(mut v: i32) {
    let thousands: i32 = v / 1000;
    v %= 1000;
    let hundreds: i32 = v / 100;
    v %= 100;
    let tens: i32 = v / 10;
    let ones: i32 = v % 10;
    if thousands > 0 {
        draw_thousands(thousands);
    }
    if hundreds > 0 {
        draw_hundreds(hundreds);
    }
    if tens > 0 {
        draw_tens(tens);
    }
    if ones > 0 {
        draw_ones(ones);
    }
}

/// Test the drawings as outout to stdout.
fn test_output(n: i32) {
    println!("{n}");
    init_n();
    draw(n);
    unsafe {
        for line in CANVAS.iter() {
            for c in line.iter() {
                print!("{}", *c);
            }
            println!();
        }
    }
    println!("\n");
}

fn main() {
    for n in [0, 1, 20, 300, 2022, 4000, 5555, 6789, 9999] {
        test_output(n);
    }
}
Output:
0
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #


1
     ######
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #


20
     #
     #
     #
     #
######
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #


300
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #    #
     #   #
     #  #
     # #
     ##


2022
     #
     #
     #
     #
###########
     #
     #
     #
     #
     #
######
     #
     #
     #
     #


4000
     #
     #
     #
     #
     #
     #
     #
     #
     #
     #
    ##
   # #
  #  #
 #   #
#    #


5555
###########
 #   #   #     
  #  #  #
   # # #
    ###
     #
     #
     #
     #
     #
    ###
   # # #
  #  #  #
 #   #   #
###########


6789
#    ######
#    #    #
#    #    #
#    #    #
###########
     #
     #
     #
     #
     #
#    #    #
#    #    #
#    #    #
#    #    #
#    ######


9999
###########
#    #    #
#    #    #
#    #    #
###########
     #
     #
     #
     #
     #
###########
#    #    #
#    #    #
#    #    #
###########

Wren

Library: Wren-fmt

This draws each Cistercian numeral on the terminal within a grid of 15 rows by 11 columns. The vertical line segment is drawn at column 5 (zero indexed) so there are 5 columns at either side.

import "./fmt" for Fmt

var n  

var init = Fn.new {
    n = List.filled(15, null)
    for (i in 0..14) {
        n[i] = List.filled(11, " ")
        n[i][5] = "x"
    }
}

var horiz = Fn.new { |c1, c2, r| (c1..c2).each { |c| n[r][c] = "x" } }
var verti = Fn.new { |r1, r2, c| (r1..r2).each { |r| n[r][c] = "x" } }
var diagd = Fn.new { |c1, c2, r| (c1..c2).each { |c| n[r+c-c1][c] = "x" } }
var diagu = Fn.new { |c1, c2, r| (c1..c2).each { |c| n[r-c+c1][c] = "x" } }

var draw // map contains recursive closures
draw = {
    1: Fn.new { horiz.call(6, 10, 0) },
    2: Fn.new { horiz.call(6, 10, 4) },
    3: Fn.new { diagd.call(6, 10, 0) },
    4: Fn.new { diagu.call(6, 10, 4) },
    5: Fn.new {
           draw[1].call()
           draw[4].call()
       },
    6: Fn.new { verti.call(0, 4, 10) },
    7: Fn.new {
           draw[1].call()
           draw[6].call()
       },
    8: Fn.new {
           draw[2].call()
           draw[6].call()
       },
    9: Fn.new {
           draw[1].call()
           draw[8].call()
       },
    10: Fn.new { horiz.call(0, 4, 0) },
    20: Fn.new { horiz.call(0, 4, 4) },
    30: Fn.new { diagu.call(0, 4, 4) },
    40: Fn.new { diagd.call(0, 4, 0) },
    50: Fn.new {
           draw[10].call()
           draw[40].call()
        },
    60: Fn.new { verti.call(0, 4, 0) },
    70: Fn.new {
           draw[10].call()
           draw[60].call()
        },
    80: Fn.new {
           draw[20].call()
           draw[60].call()
        },
    90: Fn.new {
           draw[10].call()
           draw[80].call()
        },
    100: Fn.new { horiz.call(6, 10, 14) },
    200: Fn.new { horiz.call(6, 10, 10) },
    300: Fn.new { diagu.call(6, 10, 14) },
    400: Fn.new { diagd.call(6, 10, 10) },
    500: Fn.new {
            draw[100].call()
            draw[400].call()
         },
    600: Fn.new { verti.call(10, 14, 10) },
    700: Fn.new {
            draw[100].call()
            draw[600].call()
         },
    800: Fn.new {
            draw[200].call()
            draw[600].call()
         },
    900: Fn.new {
            draw[100].call()
            draw[800].call()
         },
    1000: Fn.new { horiz.call(0, 4, 14) },
    2000: Fn.new { horiz.call(0, 4, 10) },
    3000: Fn.new { diagd.call(0, 4, 10) },
    4000: Fn.new { diagu.call(0, 4, 14) },
    5000: Fn.new {
             draw[1000].call()
             draw[4000].call()
          },
    6000: Fn.new { verti.call(10, 14, 0) },
    7000: Fn.new {
             draw[1000].call()
             draw[6000].call()
          },
    8000: Fn.new {
             draw[2000].call()
             draw[6000].call()
          },
    9000: Fn.new {
             draw[1000].call()
             draw[8000].call()
          }
}

var numbers = [0, 1, 20, 300, 4000, 5555, 6789, 9999]
for (number in numbers) {
    init.call()
    System.print("%(number):")
    var thousands = (number/1000).floor
    number = number % 1000
    var hundreds  = (number/100).floor
    number = number % 100
    var tens = (number/10).floor
    var ones = number % 10
    if (thousands > 0) draw[thousands*1000].call()
    if (hundreds > 0) draw[hundreds*100].call()
    if (tens > 0) draw[tens*10].call()
    if (ones > 0) draw[ones].call()
    Fmt.mprint(n, 1, 0, "")
    System.print()
}
Output:
0:
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          

1:
          x x x x x x
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          

20:
          x          
          x          
          x          
          x          
x x x x x x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          

300:
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x         x
          x       x  
          x     x    
          x   x      
          x x        

4000:
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
          x          
        x x          
      x   x          
    x     x          
  x       x          
x         x          

5555:
x x x x x x x x x x x
  x       x       x  
    x     x     x    
      x   x   x      
        x x x        
          x          
          x          
          x          
          x          
          x          
        x x x        
      x   x   x      
    x     x     x    
  x       x       x  
x x x x x x x x x x x

6789:
x         x x x x x x
x         x         x
x         x         x
x         x         x
x x x x x x x x x x x
          x          
          x          
          x          
          x          
          x          
x         x         x
x         x         x
x         x         x
x         x         x
x         x x x x x x

9999:
x x x x x x x x x x x
x         x         x
x         x         x
x         x         x
x x x x x x x x x x x
          x          
          x          
          x          
          x          
          x          
x x x x x x x x x x x
x         x         x
x         x         x
x         x         x
x x x x x x x x x x x