Skip to content

Commit 4ab44f2

Browse files
committed
Added loading from font files
Split animation base into Gfx
1 parent 6f5fda6 commit 4ab44f2

9 files changed

Lines changed: 161 additions & 114 deletions

File tree

book/literacy.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ https://github.com/calaldees/teacherEducation/blob/acbc846eddf56268305380145fb6e
1616
Secret Literacy: David Dido (citation)
1717

1818
Paper
19+
1920
explicit
2021

2122
Explain why you should do it one way over another
@@ -37,4 +38,11 @@ Passive voice considered harmful
3738

3839
---
3940

40-
in Computing Programming language literacy is Computing Literacy - it's the language of computers - do you speak it?
41+
in Computing Programming language literacy is Computing Literacy - it's the language of computers - do you speak it?
42+
43+
44+
* Data Volumetrics, Permutations
45+
* Assignment, selection, iteration, set, dictionary, array, call stack depth, stack, queue, cache invalidation, proxy
46+
* Time Complexity, Parallelisation, Race hazard, Overloading of mathematical operators, Order of precedence, Wall Time, Worst case time complexity,
47+
48+
same as / does the same thing | functionally equivalent

teachprogramming/static/language_reference/languages/javascript/javascript.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,10 @@ function sort() {
525525

526526
}
527527

528+
function regex() {
529+
// TODO
530+
}
531+
528532
function assertion() {
529533
let name = "bob"
530534
let count = 3
@@ -597,6 +601,7 @@ function main() {
597601
split_strings();
598602
random_number();
599603
sort();
604+
regex();
600605
assertion();
601606
http_request_json();
602607
debug();

teachprogramming/static/projects/game/animation_base.js

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,47 @@
1+
class Gfx {
2+
static load_image(url) {return new Promise((resolve, reject)=>{
3+
const img = new Image()
4+
img.onload = () => resolve(img)
5+
img.onerror = (e) => console.error(`image load failed ${url}`, e)
6+
img.src = url
7+
})}
8+
static drawLine(c, x1,y1,x2,y2,color='white',lineWidth=1) {
9+
c.strokeStyle = color
10+
c.lineWidth = lineWidth
11+
c.beginPath()
12+
c.moveTo(x1, y1)
13+
c.lineTo(x2, y2)
14+
c.stroke()
15+
}
16+
static subsurface(img, x, y, width, height) {
17+
// TODO: consider using https://developer.mozilla.org/en-US/docs/Web/API/Window/createImageBitmap
18+
const o = new OffscreenCanvas(width, height)
19+
const c = o.getContext("2d")
20+
c.drawImage(img, -x, -y)
21+
return o.transferToImageBitmap()
22+
}
23+
static invert(img) {
24+
const o1 = new OffscreenCanvas(img.width, img.height)
25+
const c1 = o1.getContext("2d")
26+
// Invert image (but destroys transparency)
27+
c1.drawImage(img, 0, 0)
28+
c1.globalCompositeOperation='difference'
29+
c1.fillStyle='white'
30+
c1.fillRect(0, 0, img.width, img.height)
31+
// Mask
32+
const o2 = new OffscreenCanvas(img.width, img.height)
33+
const c2 = o2.getContext('2d')
34+
c2.drawImage(img, 0, 0);
35+
c2.globalCompositeOperation = 'source-in';
36+
c2.fillStyle = 'black';
37+
c2.fillRect(0, 0, img.width, img.height);
38+
// Cut Mask out of Inverted image
39+
c1.globalCompositeOperation = 'destination-in';
40+
c1.drawImage(o2, 0, 0);
41+
return o1.transferToImageBitmap()
42+
}
43+
}
44+
145
function createFullScreenCanvasElement({width=480, height=270, background_color='black'}={}) {
246
const body_style = `
347
margin: 0;
@@ -38,7 +82,7 @@ function createAudioElement() {
3882
//play_audio = (url) => {this.audio.src = url}
3983
}
4084

41-
export class CanvasAnimationBase {
85+
class CanvasAnimationBase {
4286
constructor(canvas=undefined, fps=60, canvas_attrs={background_color: 'black'}) {
4387
this.canvas = canvas || createFullScreenCanvasElement(canvas_attrs)
4488
this.context = this.canvas.getContext('2d', { alpha: true })
@@ -91,20 +135,8 @@ export class CanvasAnimationBase {
91135
}
92136
}
93137

94-
load_image = (name, url) => {
95-
let resolve_image_loaded = undefined
96-
const promise = new Promise((resolve, reject) => {
97-
resolve_image_loaded = resolve
98-
})
99-
const images = this.images
100-
images[name] = new Image()
101-
images[name].onload = function() {
102-
images[name] = this
103-
resolve_image_loaded(this)
104-
}
105-
images[name].src = url
106-
return promise
107-
}
138+
// TODO! In making this async, many of the earlier examples will not function and need reworking
139+
async load_image(name, url) {this.images[name] = await Gfx.load_image(url)}
108140

109141
run = (time) => {
110142
if (this.keys_pressed.has("Escape")) {this.setRunning(false)}
@@ -125,45 +157,7 @@ export class CanvasAnimationBase {
125157
throw new Error("Not Implemented Error")
126158
}
127159

128-
// Graphics Utils ----------------------------------------------------------
129-
130-
static drawLine(c, x1,y1,x2,y2,color='white',lineWidth=1) {
131-
c.strokeStyle = color
132-
c.lineWidth = lineWidth
133-
c.beginPath()
134-
c.moveTo(x1, y1)
135-
c.lineTo(x2, y2)
136-
c.stroke()
137-
}
138-
139-
static subsurface(img, x, y, width, height) {
140-
const o = new OffscreenCanvas(width, height)
141-
const c = o.getContext("2d")
142-
c.drawImage(img, -x, -y)
143-
return o.transferToImageBitmap()
144-
}
145-
146-
static invert(img) {
147-
const o1 = new OffscreenCanvas(img.width, img.height)
148-
const c1 = o1.getContext("2d")
149-
// Invert image (but destroys transparency)
150-
c1.drawImage(img, 0, 0)
151-
c1.globalCompositeOperation='difference'
152-
c1.fillStyle='white'
153-
c1.fillRect(0, 0, img.width, img.height)
154-
155-
// Mask
156-
const o2 = new OffscreenCanvas(img.width, img.height)
157-
const c2 = o2.getContext('2d')
158-
c2.drawImage(img, 0, 0);
159-
c2.globalCompositeOperation = 'source-in';
160-
c2.fillStyle = 'black';
161-
c2.fillRect(0, 0, img.width, img.height);
160+
}
162161

163-
// Cut Mask out of Inverted image
164-
c1.globalCompositeOperation = 'destination-in';
165-
c1.drawImage(o2, 0, 0);
166-
return o1.transferToImageBitmap()
167-
}
168162

169-
}
163+
export {Gfx, CanvasAnimationBase}

teachprogramming/static/projects/game/copter_test.py

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
*.gif
22
*.webp
3+
*.draw
4+
*.yaff
35
animation_base_pygame.py
46
animation_base.js

teachprogramming/static/projects/graphics/font/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ http.server: animation_base.js
3131

3232
font.webp:
3333
curl https://img.damieng.com/fonts/ch8-previews/Pristine.webp -o font.webp
34+
35+
FONT_REPO:=https://raw.githubusercontent.com/robhagemans/hoard-of-bitfonts/refs/heads/master
36+
amstrad_cpc.draw:
37+
curl -O ${FONT_REPO}/amstrad/amstrad_cpc.draw
38+
curl -O ${FONT_REPO}/acorn/bbc/bbc_micro.yaff
39+
curl -O ${FONT_REPO}/sinclair/zx-spectrum.yaff
40+
curl -O ${FONT_REPO}/commodore/c64-c16-c128/c64.yaff
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function* range(target, start=0, step=1) {for (let i=start ; i<target ; i+=step) {yield i}}
2+
function* enumerate(iterable) {let count = 0; for (let item of iterable) {yield [count++, item]}}
3+
function all(iterable) {for (let i of iterable) {if (!i) {return false}} return true}
4+
function* zip(...iterables) {
5+
const iterators = [...iterables].map(iterable => iterable[Symbol.iterator]());
6+
while (true) {
7+
const iterable_items = iterators.map(iterator => iterator.next());
8+
if (all(iterable_items.map(i => i.done))) {break;}
9+
yield iterable_items.map(i => i.value);
10+
}
11+
}
12+
function clear_object(o) {Object.keys(o).forEach(k => {delete o[k]})}
13+
async function fetch_text(path) {return await (await fetch(path)).text()}
14+
15+
export {
16+
range,
17+
enumerate,
18+
all,
19+
zip,
20+
clear_object,
21+
fetch_text,
22+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { range, zip } from './core.js'
2+
import { Gfx } from './animation_base.js'
3+
4+
// From Img --------------------------------------------------------------------
5+
6+
export const SEQUENCE_DAMIENG = ` !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_£abcdefghijklmnopqrstuvwxyz{|}~©`.replace('\n','')
7+
8+
export function cut_font_chars_from_img(img, char_seq, w=8, h=8) {
9+
const [ww, hh] = [img.width, img.height]
10+
return Object.fromEntries(
11+
[...range(Math.min(Math.floor(ww/w)*Math.floor(hh/h),char_seq.length))].map((i)=>[
12+
char_seq[i], Gfx.subsurface(img, (i*w)%ww, Math.floor((i*w)/ww)*h, w, h)
13+
])
14+
)
15+
}
16+
17+
// From text (yaff,draw) -------------------------------------------------------
18+
19+
function* extract_font_text_chars(text, foreground='@', background='.', width=8, height=8) {
20+
text = text.replaceAll(/\s/sg, '')
21+
const REGEX_CHAR = new RegExp(String.raw`[${background}${foreground}]{${width*height}}`, "isg")
22+
for (let match of text.matchAll(REGEX_CHAR)) {yield match[0]}
23+
}
24+
function* extract_unicode_sequence(text) {
25+
const REGEX_UNICODE = new RegExp(String.raw`u\+([0123456789abcdef]+):`, "ig")
26+
for (let match of text.matchAll(REGEX_UNICODE)) {
27+
yield String.fromCharCode(parseInt('0x'+match[1],16))
28+
}
29+
}
30+
function text_char_to_ImageData(char, foreground='@', width=8, height=8) {
31+
const [WHITE_PIXEL, BLACK_PIXEL] = [[255,255,255,255],[0,0,0,0]]
32+
const pixel_data = [...char].map(c=>c==foreground?WHITE_PIXEL:BLACK_PIXEL).flat()
33+
return new ImageData(new Uint8ClampedArray(pixel_data), width, height)
34+
}
35+
export async function parse_font_unicode(data, char_seq=undefined) {
36+
return Object.fromEntries(zip(
37+
char_seq || extract_unicode_sequence(data),
38+
await Promise.all(
39+
[...extract_font_text_chars(data)]
40+
.map(char=>text_char_to_ImageData(char))
41+
.map(async (img_data)=>await createImageBitmap(img_data)),
42+
)
43+
))
44+
}

teachprogramming/static/projects/graphics/font/font_mask.html

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,9 @@
66
</head>
77
<body></body>
88
<script type="module">
9-
import {CanvasAnimationBase} from './animation_base.js'
10-
11-
function* range(target, start=0, step=1) {for (let i=start ; i<target ; i+=step) {yield i}}
12-
function* enumerate(iterable) {let count = 0; for (let item of iterable) {yield [count++, item]}}
13-
function clear_object(o) {Object.keys(o).forEach(k => {delete o[k]})}
14-
15-
16-
const SEQUENCE_DAMIENG = ` !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_£abcdefghijklmnopqrstuvwxyz{|}~©`
17-
9+
import { enumerate, clear_object, fetch_text } from './core.js'
10+
import { CanvasAnimationBase, Gfx } from './animation_base.js'
11+
import { cut_font_chars_from_img, parse_font_unicode, SEQUENCE_DAMIENG } from './font_loader.js'
1812

1913
class Surface {
2014
constructor(width, height) {
@@ -68,44 +62,42 @@
6862
this.b.c.drawImage(this.mask.o, 0, 0)
6963
this.b.c.restore()
7064
}
65+
cut_and_draw(context) {
66+
this.cut()
67+
context.drawImage(this.a.o,0,0)
68+
context.drawImage(this.b.o,0,0)
69+
}
7170
}
7271

72+
7373
class Font {
74-
constructor(img) {
75-
this.font = this._load_font_advance(img)
74+
constructor(font_char_img) {
75+
this.font = font_char_img
7676
this.char_cache = {}
7777

7878
this.background = new Surface(8, 8)
7979
const bc = this.background.c
8080
bc.fillStyle='green'
81-
bc.fillRect(0, 0, img.width, img.height)
81+
bc.fillRect(0, 0, bc.width, bc.height)
8282
}
8383

8484
clear_cache() {
8585
clear_object(this.char_cache)
8686
}
8787

88-
_load_font_advance(img) {
89-
const [w, h, seq] = [8, 8, SEQUENCE_DAMIENG]
90-
const [ww, hh] = [img.width, img.height]
91-
return Object.fromEntries(
92-
[...range(Math.min(Math.floor(ww/w)*Math.floor(hh/h),seq.length))].map((i)=>[
93-
seq[i], CanvasAnimationBase.subsurface(img, (i*w)%ww, Math.floor((i*w)/ww)*h, w, h)
94-
])
95-
)
96-
}
97-
9888
render_mask_img(mask, img) {
9989
const s = new Surface(mask.width, mask.height)
90+
s.c.save()
10091
s.c.drawImage(img, 0, 0)
10192
s.c.globalCompositeOperation = 'destination-in';
10293
s.c.drawImage(mask, 0, 0)
94+
s.c.restore()
10395
return s.img
10496
}
10597

10698
draw_char_mask(c, char, x, y) {
10799
let img
108-
if (!(img = this.char_cache[char])) {
100+
if (!(img = this.char_cache[char])) { // cache miss on font char
109101
img = this.render_mask_img(this.font[char], this.background.o)
110102
this.char_cache[char] = img
111103
}
@@ -129,7 +121,10 @@
129121
}
130122

131123
async constructor_async() {
132-
this.font = new Font(CanvasAnimationBase.invert(await this.load_image("font", "Babyteeth.webp")))
124+
this.font = new Font(cut_font_chars_from_img(Gfx.invert(await Gfx.load_image("Babyteeth.webp")), SEQUENCE_DAMIENG))
125+
this.font = new Font(await parse_font_unicode(await fetch_text('zx-spectrum.yaff')))
126+
//this.font2 = parse_font_text(fetch('amstrad_cpc.draw'))
127+
//this.font3 = parse_font_text(fetch('bbc_micro.yaff'))
133128
}
134129

135130
model_inc(frame) {
@@ -140,7 +135,7 @@
140135
const bc = this.font.background.c
141136
bc.fillStyle='green'; bc.fillRect(0,0,8,8);
142137
let f = Math.abs(Math.floor(Math.sin(frame/64)*15))
143-
CanvasAnimationBase.drawLine(bc,4+f%16,-4,-4,4+f%16,'#00FF00')
138+
Gfx.drawLine(bc,4+f%16,-4,-4,4+f%16,'#00FF00')
144139

145140
this.a.clear()
146141
this.font.draw_font(this.a.c, "Hello, What is this! OMG OMG", 20, 20)
@@ -153,16 +148,12 @@
153148
this.mix.mask.c.fillStyle = 'white'
154149
this.mix.mask.c.fillRect(0,0,frame%this.w,this.h)
155150

156-
this.mix.cut()
157-
158151
this.clear()
159-
context.drawImage(this.a.o,0,0)
160-
context.drawImage(this.b.o,0,0)
161-
162-
CanvasAnimationBase.drawLine(context, frame%this.w,0,frame%this.w,this.h,'white',4)
163-
}
152+
this.mix.cut_and_draw(context)
164153

165154

155+
Gfx.drawLine(context, frame%this.w,0,frame%this.w,this.h,'white',4)
156+
}
166157
}
167158

168159
const main = new FontCanvas()

0 commit comments

Comments
 (0)