add u8g2 font renderings

pull/1/head
flop 4 weeks ago
parent b9fa8ba3ac
commit 9d48c79c0b
  1. 0
      ts/src/font/fonts/5x7.ts
  2. 0
      ts/src/font/fonts/test.ts
  3. 137
      ts/src/font/fonts/u8g2_font_5x7_tf.u8g2font.ts
  4. 7
      ts/src/font/index.ts
  5. 10
      ts/src/font/types.ts
  6. 548
      ts/src/font/u8g2.ts
  7. 4
      ts/src/renderer.ts
  8. 256
      ts/src/types.ts

@ -0,0 +1,137 @@
export const u8g2_font_5x7_tf_u8g2font: number[] = [
0xbf, 0x00, 0x02, 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x05, 0x07, 0x00,
0xff, 0x06, 0xff, 0x06, 0x00, 0x01, 0x0a, 0x02, 0x16, 0x06, 0x2f, 0x20,
0x05, 0x00, 0xbd, 0x01, 0x21, 0x06, 0xb1, 0xb1, 0x19, 0x29, 0x22, 0x07,
0x5b, 0xb7, 0x49, 0x56, 0x00, 0x23, 0x0a, 0x2d, 0xb1, 0xab, 0x86, 0xaa,
0x86, 0xaa, 0x00, 0x24, 0x09, 0x2d, 0xb1, 0x5b, 0xf5, 0x4e, 0x69, 0x01,
0x25, 0x08, 0x34, 0xb1, 0xc9, 0xb1, 0x77, 0x00, 0x26, 0x09, 0x2c, 0xb1,
0x8b, 0x29, 0x56, 0x31, 0x05, 0x27, 0x05, 0x99, 0xb7, 0x19, 0x28, 0x07,
0x72, 0xb1, 0x53, 0xcd, 0x00, 0x29, 0x08, 0x72, 0xb1, 0x89, 0xa9, 0x52,
0x00, 0x2a, 0x07, 0x6b, 0xb1, 0x49, 0xd5, 0x6a, 0x2b, 0x0a, 0x2d, 0xb1,
0xcd, 0x28, 0x0e, 0x99, 0x51, 0x04, 0x2c, 0x07, 0x5b, 0xaf, 0x53, 0x25,
0x00, 0x2d, 0x06, 0x0c, 0xb5, 0x19, 0x01, 0x2e, 0x06, 0x52, 0xb1, 0x19,
0x01, 0x2f, 0x07, 0x24, 0xb3, 0x8f, 0x6d, 0x00, 0x30, 0x08, 0x73, 0xb1,
0xab, 0x5c, 0x15, 0x00, 0x31, 0x07, 0x73, 0xb1, 0x4b, 0xb2, 0x35, 0x32,
0x09, 0x34, 0xb1, 0x53, 0x31, 0xc7, 0x72, 0x04, 0x33, 0x0a, 0x34, 0xb1,
0x19, 0x39, 0x69, 0x24, 0x93, 0x02, 0x34, 0x0a, 0x34, 0xb1, 0x8d, 0xaa,
0x1a, 0x31, 0x27, 0x00, 0x35, 0x0a, 0x34, 0xb1, 0x19, 0x7a, 0x23, 0x99,
0x14, 0x00, 0x36, 0x0a, 0x34, 0xb1, 0x53, 0x79, 0x45, 0x99, 0x14, 0x00,
0x37, 0x0a, 0x34, 0xb1, 0x19, 0x39, 0xe6, 0x98, 0x23, 0x00, 0x38, 0x0a,
0x34, 0xb1, 0x53, 0x31, 0xa9, 0x28, 0x93, 0x02, 0x39, 0x0a, 0x34, 0xb1,
0x53, 0x51, 0xa6, 0x9d, 0x14, 0x00, 0x3a, 0x07, 0x6a, 0xb1, 0x19, 0x71,
0x04, 0x3b, 0x08, 0x33, 0xaf, 0xb3, 0x91, 0x2a, 0x01, 0x3c, 0x07, 0x6b,
0xb1, 0x4d, 0x75, 0x01, 0x3d, 0x08, 0x1c, 0xb3, 0x19, 0x19, 0x8d, 0x00,
0x3e, 0x07, 0x6b, 0xb1, 0xc9, 0x55, 0x09, 0x3f, 0x09, 0x73, 0xb1, 0x6b,
0xa6, 0x0c, 0x13, 0x00, 0x40, 0x09, 0x34, 0xb1, 0x53, 0x51, 0xdd, 0x48,
0x01, 0x41, 0x09, 0x34, 0xb1, 0x53, 0x51, 0x8e, 0x29, 0x03, 0x42, 0x0a,
0x34, 0xb1, 0x59, 0x71, 0xa4, 0x28, 0x47, 0x02, 0x43, 0x09, 0x34, 0xb1,
0x53, 0x51, 0x97, 0x49, 0x01, 0x44, 0x09, 0x34, 0xb1, 0x59, 0xd1, 0x39,
0x12, 0x00, 0x45, 0x09, 0x34, 0xb1, 0x19, 0x7a, 0xe5, 0x3c, 0x02, 0x46,
0x08, 0x34, 0xb1, 0x19, 0x7a, 0xe5, 0x1a, 0x47, 0x09, 0x34, 0xb1, 0x53,
0x51, 0xa7, 0x99, 0x06, 0x48, 0x08, 0x34, 0xb1, 0x89, 0x72, 0x4c, 0x33,
0x49, 0x07, 0x73, 0xb1, 0x59, 0xb1, 0x35, 0x4a, 0x08, 0x34, 0xb1, 0x6f,
0xcb, 0xa4, 0x00, 0x4b, 0x0a, 0x34, 0xb1, 0x89, 0x2a, 0x49, 0x99, 0xca,
0x00, 0x4c, 0x07, 0x34, 0xb1, 0xc9, 0xdd, 0x23, 0x4d, 0x09, 0x34, 0xb1,
0x89, 0xe3, 0x88, 0x66, 0x00, 0x4e, 0x08, 0x34, 0xb1, 0x89, 0x6b, 0xa9,
0x33, 0x4f, 0x09, 0x34, 0xb1, 0x53, 0xd1, 0x99, 0x14, 0x00, 0x50, 0x0a,
0x34, 0xb1, 0x59, 0x51, 0x8e, 0x94, 0x33, 0x00, 0x51, 0x0a, 0x3c, 0xaf,
0x53, 0xd1, 0x5c, 0x49, 0xa3, 0x00, 0x52, 0x09, 0x34, 0xb1, 0x59, 0x51,
0x8e, 0xd4, 0x0c, 0x53, 0x0a, 0x34, 0xb1, 0x53, 0x31, 0x65, 0x54, 0x26,
0x05, 0x54, 0x07, 0x73, 0xb1, 0x59, 0xb1, 0x0b, 0x55, 0x08, 0x34, 0xb1,
0x89, 0x9e, 0x49, 0x01, 0x56, 0x09, 0x34, 0xb1, 0x89, 0xce, 0x24, 0x15,
0x00, 0x57, 0x09, 0x34, 0xb1, 0x89, 0xe6, 0x38, 0x62, 0x00, 0x58, 0x0a,
0x34, 0xb1, 0x89, 0x32, 0x49, 0x15, 0x65, 0x00, 0x59, 0x08, 0x73, 0xb1,
0x49, 0x56, 0x59, 0x01, 0x5a, 0x09, 0x34, 0xb1, 0x19, 0x39, 0xb6, 0x47,
0x00, 0x5b, 0x07, 0x73, 0xb1, 0x19, 0xb1, 0x39, 0x5c, 0x09, 0x24, 0xb3,
0xc9, 0x28, 0xa3, 0x8c, 0x02, 0x5d, 0x07, 0x73, 0xb1, 0x99, 0xcd, 0x11,
0x5e, 0x05, 0x53, 0xb9, 0x6b, 0x5f, 0x06, 0x0c, 0xb1, 0x19, 0x01, 0x60,
0x06, 0x52, 0xb9, 0x89, 0x01, 0x61, 0x08, 0x24, 0xb1, 0x1b, 0x51, 0xa9,
0x02, 0x62, 0x0a, 0x34, 0xb1, 0xc9, 0x79, 0x45, 0x39, 0x12, 0x00, 0x63,
0x06, 0x23, 0xb1, 0x9b, 0x59, 0x64, 0x08, 0x34, 0xb1, 0xaf, 0x46, 0x94,
0x69, 0x65, 0x08, 0x24, 0xb1, 0x53, 0x69, 0x64, 0x05, 0x66, 0x09, 0x34,
0xb1, 0xad, 0xca, 0x99, 0x23, 0x00, 0x67, 0x09, 0x2c, 0xaf, 0x1b, 0x31,
0xa9, 0x8c, 0x06, 0x68, 0x08, 0x34, 0xb1, 0xc9, 0x79, 0x45, 0x33, 0x69,
0x08, 0x73, 0xb1, 0xcb, 0x48, 0x56, 0x03, 0x6a, 0x09, 0x7b, 0xaf, 0xcd,
0xb0, 0x54, 0x15, 0x00, 0x6b, 0x09, 0x34, 0xb1, 0xc9, 0x55, 0x92, 0xa9,
0x0c, 0x6c, 0x07, 0x73, 0xb1, 0x91, 0x5d, 0x03, 0x6d, 0x08, 0x24, 0xb1,
0x49, 0x69, 0x48, 0x19, 0x6e, 0x07, 0x24, 0xb1, 0x59, 0xd1, 0x0c, 0x6f,
0x08, 0x24, 0xb1, 0x53, 0x51, 0x26, 0x05, 0x70, 0x09, 0x2c, 0xaf, 0x59,
0x51, 0x8e, 0x94, 0x01, 0x71, 0x08, 0x2c, 0xaf, 0x1b, 0x51, 0xa6, 0x1d,
0x72, 0x08, 0x24, 0xb1, 0x59, 0x51, 0x67, 0x00, 0x73, 0x08, 0x24, 0xb1,
0x1b, 0x1a, 0x0d, 0x05, 0x74, 0x09, 0x34, 0xb1, 0xcb, 0x71, 0xe6, 0x8c,
0x04, 0x75, 0x07, 0x24, 0xb1, 0x89, 0x66, 0x1a, 0x76, 0x07, 0x63, 0xb1,
0x49, 0x56, 0x05, 0x77, 0x07, 0x24, 0xb1, 0x89, 0x72, 0x1c, 0x78, 0x08,
0x24, 0xb1, 0x89, 0x49, 0xaa, 0x18, 0x79, 0x09, 0x2c, 0xaf, 0x89, 0x32,
0x95, 0x25, 0x00, 0x7a, 0x08, 0x24, 0xb1, 0x19, 0xb1, 0x1c, 0x01, 0x7b,
0x08, 0x73, 0xb1, 0x4d, 0x49, 0xd6, 0x01, 0x7c, 0x05, 0xb1, 0xb1, 0x39,
0x7d, 0x09, 0x73, 0xb1, 0xc9, 0x51, 0xc5, 0x14, 0x01, 0x7e, 0x07, 0x14,
0xb9, 0x4b, 0x2a, 0x01, 0xa0, 0x05, 0x00, 0xbd, 0x01, 0xa1, 0x06, 0xb1,
0xb1, 0x49, 0x23, 0xa2, 0x09, 0x34, 0xaf, 0x8d, 0x23, 0x35, 0x67, 0x02,
0xa3, 0x08, 0x2c, 0xb1, 0x55, 0x71, 0x56, 0x02, 0xa4, 0x0a, 0x2d, 0xb1,
0xc9, 0x69, 0xa6, 0xb8, 0x72, 0x00, 0xa5, 0x09, 0x73, 0xb1, 0x49, 0xaa,
0x5a, 0x31, 0x01, 0xa6, 0x06, 0xa9, 0xb1, 0x51, 0x02, 0xa7, 0x08, 0x7b,
0xaf, 0x9b, 0xaa, 0x92, 0x0b, 0xa8, 0x06, 0x4b, 0xbb, 0x49, 0x01, 0xa9,
0x0b, 0x3d, 0xaf, 0x5b, 0x59, 0xa5, 0xa9, 0x92, 0x4e, 0x0b, 0xaa, 0x06,
0x1b, 0xb7, 0x5b, 0x49, 0xab, 0x07, 0x1d, 0xb3, 0x8b, 0xb2, 0x01, 0xac,
0x06, 0x14, 0xb3, 0x19, 0x39, 0xad, 0x05, 0x4b, 0xb5, 0x19, 0xae, 0x0b,
0x3d, 0xaf, 0x5b, 0x79, 0xa4, 0x39, 0x75, 0x5a, 0x00, 0xaf, 0x06, 0x0c,
0xbb, 0x19, 0x01, 0xb0, 0x06, 0x5b, 0xb7, 0xeb, 0x02, 0xb1, 0x0b, 0x35,
0xb1, 0xcd, 0x28, 0x0e, 0x99, 0x51, 0x1c, 0x02, 0xb2, 0x06, 0x62, 0xb5,
0x51, 0x06, 0xb3, 0x06, 0x62, 0xb5, 0x19, 0x69, 0xb4, 0x06, 0x52, 0xb9,
0x53, 0x00, 0xb5, 0x08, 0x2c, 0xaf, 0x89, 0xe6, 0x48, 0x19, 0xb6, 0x08,
0x34, 0xb1, 0x1b, 0x6a, 0xf5, 0x03, 0xb7, 0x06, 0x52, 0xb5, 0x19, 0x01,
0xb8, 0x06, 0x52, 0xaf, 0x53, 0x00, 0xb9, 0x07, 0x63, 0xb5, 0x4b, 0x32,
0x0d, 0xba, 0x06, 0x1b, 0xb7, 0xeb, 0x02, 0xbb, 0x08, 0x1d, 0xb3, 0x89,
0xa5, 0x4c, 0x00, 0xbc, 0x09, 0x3c, 0xaf, 0xc9, 0xcd, 0xa8, 0x76, 0x00,
0xbd, 0x09, 0x3c, 0xaf, 0xc9, 0x2d, 0x1d, 0xb3, 0x00, 0xbe, 0x0a, 0x3c,
0xaf, 0x91, 0x3a, 0xaa, 0xa8, 0x76, 0x00, 0xbf, 0x09, 0x73, 0xb1, 0xcb,
0x30, 0xc5, 0x54, 0x01, 0xc0, 0x09, 0x34, 0xb1, 0x53, 0x51, 0x8e, 0x29,
0x03, 0xc1, 0x09, 0x34, 0xb1, 0x53, 0x51, 0x8e, 0x29, 0x03, 0xc2, 0x09,
0x34, 0xb1, 0x53, 0x51, 0x8e, 0x29, 0x03, 0xc3, 0x09, 0x34, 0xb1, 0x53,
0x51, 0x8e, 0x29, 0x03, 0xc4, 0x0a, 0x34, 0xb1, 0x89, 0x49, 0xc5, 0x31,
0x65, 0x00, 0xc5, 0x09, 0x34, 0xb1, 0x93, 0x2a, 0x8e, 0x29, 0x03, 0xc6,
0x09, 0x34, 0xb1, 0x1b, 0xa9, 0x1a, 0xaa, 0x25, 0xc7, 0x0a, 0x3c, 0xaf,
0x53, 0x51, 0x97, 0x49, 0x46, 0x00, 0xc8, 0x09, 0x34, 0xb1, 0x19, 0x7a,
0xe5, 0x3c, 0x02, 0xc9, 0x09, 0x34, 0xb1, 0x19, 0x7a, 0xe5, 0x3c, 0x02,
0xca, 0x09, 0x34, 0xb1, 0x19, 0x7a, 0xe5, 0x3c, 0x02, 0xcb, 0x09, 0x34,
0xb1, 0x19, 0x7a, 0xe5, 0x3c, 0x02, 0xcc, 0x07, 0x73, 0xb1, 0x59, 0xb1,
0x35, 0xcd, 0x07, 0x73, 0xb1, 0x59, 0xb1, 0x35, 0xce, 0x07, 0x73, 0xb1,
0x59, 0xb1, 0x35, 0xcf, 0x07, 0x73, 0xb1, 0x59, 0xb1, 0x35, 0xd0, 0x09,
0x34, 0xb1, 0x99, 0x69, 0x75, 0x8d, 0x04, 0xd1, 0x08, 0x34, 0xb1, 0x49,
0x73, 0xa9, 0x33, 0xd2, 0x09, 0x34, 0xb1, 0x53, 0xd1, 0x99, 0x14, 0x00,
0xd3, 0x09, 0x34, 0xb1, 0x53, 0xd1, 0x99, 0x14, 0x00, 0xd4, 0x09, 0x34,
0xb1, 0x53, 0xd1, 0x99, 0x14, 0x00, 0xd5, 0x09, 0x34, 0xb1, 0x53, 0xd1,
0x99, 0x14, 0x00, 0xd6, 0x0a, 0x34, 0xb1, 0x89, 0x49, 0x45, 0x33, 0x29,
0x00, 0xd7, 0x08, 0x24, 0xb1, 0x89, 0x49, 0xaa, 0x18, 0xd8, 0x09, 0x34,
0xb1, 0x1b, 0xe9, 0x48, 0x47, 0x02, 0xd9, 0x08, 0x34, 0xb1, 0x89, 0x9e,
0x49, 0x01, 0xda, 0x08, 0x34, 0xb1, 0x89, 0x9e, 0x49, 0x01, 0xdb, 0x08,
0x34, 0xb1, 0x89, 0x9e, 0x49, 0x01, 0xdc, 0x0a, 0x34, 0xb1, 0x89, 0x19,
0x45, 0x33, 0x29, 0x00, 0xdd, 0x08, 0x73, 0xb1, 0x49, 0x56, 0x59, 0x01,
0xde, 0x0a, 0x34, 0xb1, 0xc9, 0x2b, 0x8e, 0x94, 0x33, 0x00, 0xdf, 0x09,
0x34, 0xb1, 0x53, 0x51, 0x95, 0x56, 0x02, 0xe0, 0x0a, 0x34, 0xb1, 0xcb,
0x28, 0x8e, 0xa8, 0x54, 0x01, 0xe1, 0x09, 0x34, 0xb1, 0xad, 0x47, 0x54,
0xaa, 0x00, 0xe2, 0x09, 0x34, 0xb1, 0xad, 0xd2, 0x88, 0x4a, 0x15, 0xe3,
0x0a, 0x34, 0xb1, 0x4b, 0x2a, 0x8e, 0xa8, 0x54, 0x01, 0xe4, 0x09, 0x34,
0xb1, 0xab, 0xe1, 0x88, 0x4a, 0x15, 0xe5, 0x09, 0x34, 0xb1, 0x93, 0x72,
0x44, 0xa5, 0x0a, 0xe6, 0x08, 0x24, 0xb1, 0x1b, 0x69, 0xc5, 0x01, 0xe7,
0x08, 0x6b, 0xaf, 0x9b, 0x59, 0x25, 0x00, 0xe8, 0x0a, 0x34, 0xb1, 0xcb,
0x28, 0xaa, 0x34, 0xb2, 0x02, 0xe9, 0x09, 0x34, 0xb1, 0xad, 0x55, 0x1a,
0x59, 0x01, 0xea, 0x0a, 0x34, 0xb1, 0x8b, 0x29, 0xaa, 0x34, 0xb2, 0x02,
0xeb, 0x0a, 0x34, 0xb1, 0x49, 0x19, 0xab, 0x34, 0xb2, 0x02, 0xec, 0x08,
0x73, 0xb1, 0xc9, 0x49, 0x56, 0x03, 0xed, 0x07, 0x73, 0xb1, 0x2b, 0x65,
0x35, 0xee, 0x07, 0x73, 0xb1, 0xab, 0x66, 0x35, 0xef, 0x08, 0x73, 0xb1,
0x49, 0x59, 0x56, 0x03, 0xf0, 0x0a, 0x34, 0xb1, 0xcb, 0xc8, 0x8a, 0x32,
0x29, 0x00, 0xf1, 0x09, 0x34, 0xb1, 0x4b, 0x2a, 0xad, 0x68, 0x06, 0xf2,
0x0a, 0x34, 0xb1, 0xcb, 0x28, 0xaa, 0x28, 0x93, 0x02, 0xf3, 0x09, 0x34,
0xb1, 0xad, 0x55, 0x94, 0x49, 0x01, 0xf4, 0x0a, 0x34, 0xb1, 0xd3, 0x58,
0x45, 0x99, 0x14, 0x00, 0xf5, 0x0a, 0x34, 0xb1, 0x4b, 0x2a, 0xaa, 0x28,
0x93, 0x02, 0xf6, 0x0a, 0x34, 0xb1, 0xab, 0xa1, 0x8a, 0x32, 0x29, 0x00,
0xf7, 0x09, 0x2c, 0xb1, 0xd3, 0x70, 0x64, 0xa8, 0x00, 0xf8, 0x09, 0x24,
0xb1, 0x1b, 0x69, 0xa4, 0x91, 0x00, 0xf9, 0x09, 0x34, 0xb1, 0xcb, 0xa8,
0x34, 0xd3, 0x00, 0xfa, 0x07, 0x34, 0xb1, 0x6d, 0x9a, 0x69, 0xfb, 0x08,
0x34, 0xb1, 0xd3, 0x30, 0x9a, 0x69, 0xfc, 0x09, 0x34, 0xb1, 0xab, 0x51,
0x34, 0xd3, 0x00, 0xfd, 0x09, 0x3c, 0xaf, 0x6d, 0xca, 0x54, 0x96, 0x00,
0xfe, 0x0a, 0x34, 0xaf, 0xc9, 0x2b, 0xca, 0x91, 0x32, 0x00, 0xff, 0x0a,
0x3c, 0xaf, 0xab, 0x51, 0x94, 0xa9, 0x2c, 0x01, 0x00, 0x00, 0x00, 0x04,
0xff, 0xff, 0x00, 0x00
];

@ -1,5 +1,8 @@
import { u8g2_font_5x7_tf_u8g2font } from "./fonts/u8g2_font_5x7_tf.u8g2font";
import { MonoDisplayFont } from "./types";
export * from "./types";
export * as _5x7 from "./5x7";
export * from "./u8g2";
export * from "./u8g2";
export const u8g2_font_5x7_tf = new MonoDisplayFont(u8g2_font_5x7_tf_u8g2font);

@ -1,5 +1,11 @@
export class MonoDisplayFont {
public static FONT_DATA: Uint8Array;
public FONT_DATA: Uint8Array;
constructor(font_data: Uint8Array | number[]) {
this.FONT_DATA = new Uint8Array(font_data.length);
for (let i = 0; i < font_data.length; i++) {
this.FONT_DATA[i] = font_data[i] || 0;
}
}
}
export type RasterizedText = { pixels: Uint8Array; width: number; height: number };
@ -7,5 +13,5 @@ export type RasterizedText = { pixels: Uint8Array; width: number; height: number
export type RasterizeTextFunction = (
text: string,
cellHeight: number,
font?: MonoDisplayFont,
font: MonoDisplayFont,
) => RasterizedText;

@ -1,398 +1,280 @@
// ---------------------------------------------------------------------------
// U8G2 font binary format constants (header offsets)
// U8G2 font binary format decoder
// Derived from: https://github.com/olikraus/u8g2/blob/master/csrc/u8g2_font.c
// https://github.com/olikraus/u8g2/blob/master/tools/font/bdfconv/bdf_rle.c
// ---------------------------------------------------------------------------
// The U8G2 font blob layout (all offsets are byte positions):
// [0] glyph_cnt – number of glyphs
// [1] bbx_mode – bounding-box mode
// [2] bits_per_0 – RLE bits for "off" run
// [3] bits_per_1 – RLE bits for "on" run
// [4] bits_per_char_width
// [5] bits_per_char_height
// [6] bits_per_char_x
// [7] bits_per_char_y
// [8] bits_per_delta_x
// [9] max_char_width
// [10] max_char_height
// [11] x_offset (signed)
// [12] y_offset (signed)
// [13] ascent_A
// [14] descent_g (negative)
// [15] ascent_para
// [16] descent_para
// [17..18] start_pos_upper (uint16 BE) – offset of uppercase glyph block
// [19..20] start_pos_lower (uint16 BE) – offset of lowercase glyph block
// [21..22] start_pos_unicode (uint16 BE) – offset of Unicode glyph block
// Glyph records follow the three jump-table blocks.
// Each glyph record:
// • A variable-length RLE bitstream (width × height bits) – MSB first
// • Followed by a fixed-width metadata section:
// dwidth (bits_per_delta_x bits) – horizontal advance
// width (bits_per_char_width)
// height (bits_per_char_height)
// x (bits_per_char_x, signed)
// y (bits_per_char_y, signed)
// The very first two bytes of each record encode the encoding (codepoint)
// as a uint16 LE, then a uint16 LE jump offset to the next record.
import type { MonoDisplayFont } from "./types";
// ---------------------------------------------------------------------------
// Built-in 5×7 font (printable ASCII 0x20–0x7E)
// Each entry is 5 column bytes, bit 0 = top row.
// ---------------------------------------------------------------------------
// prettier-ignore
const BUILTIN_FONT_DATA: Record<number, number[]> = {
0x20: [0x00, 0x00, 0x00, 0x00, 0x00], // space
0x21: [0x00, 0x00, 0x5F, 0x00, 0x00], // !
0x22: [0x00, 0x07, 0x00, 0x07, 0x00], // "
0x23: [0x14, 0x7F, 0x14, 0x7F, 0x14], // #
0x24: [0x24, 0x2A, 0x7F, 0x2A, 0x12], // $
0x25: [0x23, 0x13, 0x08, 0x64, 0x62], // %
0x26: [0x36, 0x49, 0x55, 0x22, 0x50], // &
0x27: [0x00, 0x05, 0x03, 0x00, 0x00], // '
0x28: [0x00, 0x1C, 0x22, 0x41, 0x00], // (
0x29: [0x00, 0x41, 0x22, 0x1C, 0x00], // )
0x2A: [0x14, 0x08, 0x3E, 0x08, 0x14], // *
0x2B: [0x08, 0x08, 0x3E, 0x08, 0x08], // +
0x2C: [0x00, 0x50, 0x30, 0x00, 0x00], // ,
0x2D: [0x08, 0x08, 0x08, 0x08, 0x08], // -
0x2E: [0x00, 0x60, 0x60, 0x00, 0x00], // .
0x2F: [0x20, 0x10, 0x08, 0x04, 0x02], // /
0x30: [0x3E, 0x51, 0x49, 0x45, 0x3E], // 0
0x31: [0x00, 0x42, 0x7F, 0x40, 0x00], // 1
0x32: [0x42, 0x61, 0x51, 0x49, 0x46], // 2
0x33: [0x21, 0x41, 0x45, 0x4B, 0x31], // 3
0x34: [0x18, 0x14, 0x12, 0x7F, 0x10], // 4
0x35: [0x27, 0x45, 0x45, 0x45, 0x39], // 5
0x36: [0x3C, 0x4A, 0x49, 0x49, 0x30], // 6
0x37: [0x01, 0x71, 0x09, 0x05, 0x03], // 7
0x38: [0x36, 0x49, 0x49, 0x49, 0x36], // 8
0x39: [0x06, 0x49, 0x49, 0x29, 0x1E], // 9
0x3A: [0x00, 0x36, 0x36, 0x00, 0x00], // :
0x3B: [0x00, 0x56, 0x36, 0x00, 0x00], // ;
0x3C: [0x08, 0x14, 0x22, 0x41, 0x00], // <
0x3D: [0x14, 0x14, 0x14, 0x14, 0x14], // =
0x3E: [0x00, 0x41, 0x22, 0x14, 0x08], // >
0x3F: [0x02, 0x01, 0x51, 0x09, 0x06], // ?
0x40: [0x32, 0x49, 0x79, 0x41, 0x3E], // @
0x41: [0x7E, 0x11, 0x11, 0x11, 0x7E], // A
0x42: [0x7F, 0x49, 0x49, 0x49, 0x36], // B
0x43: [0x3E, 0x41, 0x41, 0x41, 0x22], // C
0x44: [0x7F, 0x41, 0x41, 0x22, 0x1C], // D
0x45: [0x7F, 0x49, 0x49, 0x49, 0x41], // E
0x46: [0x7F, 0x09, 0x09, 0x09, 0x01], // F
0x47: [0x3E, 0x41, 0x49, 0x49, 0x7A], // G
0x48: [0x7F, 0x08, 0x08, 0x08, 0x7F], // H
0x49: [0x00, 0x41, 0x7F, 0x41, 0x00], // I
0x4A: [0x20, 0x40, 0x41, 0x3F, 0x01], // J
0x4B: [0x7F, 0x08, 0x14, 0x22, 0x41], // K
0x4C: [0x7F, 0x40, 0x40, 0x40, 0x40], // L
0x4D: [0x7F, 0x02, 0x0C, 0x02, 0x7F], // M
0x4E: [0x7F, 0x04, 0x08, 0x10, 0x7F], // N
0x4F: [0x3E, 0x41, 0x41, 0x41, 0x3E], // O
0x50: [0x7F, 0x09, 0x09, 0x09, 0x06], // P
0x51: [0x3E, 0x41, 0x51, 0x21, 0x5E], // Q
0x52: [0x7F, 0x09, 0x19, 0x29, 0x46], // R
0x53: [0x46, 0x49, 0x49, 0x49, 0x31], // S
0x54: [0x01, 0x01, 0x7F, 0x01, 0x01], // T
0x55: [0x3F, 0x40, 0x40, 0x40, 0x3F], // U
0x56: [0x1F, 0x20, 0x40, 0x20, 0x1F], // V
0x57: [0x3F, 0x40, 0x38, 0x40, 0x3F], // W
0x58: [0x63, 0x14, 0x08, 0x14, 0x63], // X
0x59: [0x07, 0x08, 0x70, 0x08, 0x07], // Y
0x5A: [0x61, 0x51, 0x49, 0x45, 0x43], // Z
0x5B: [0x00, 0x7F, 0x41, 0x41, 0x00], // [
0x5C: [0x02, 0x04, 0x08, 0x10, 0x20], // backslash
0x5D: [0x00, 0x41, 0x41, 0x7F, 0x00], // ]
0x5E: [0x04, 0x02, 0x01, 0x02, 0x04], // ^
0x5F: [0x40, 0x40, 0x40, 0x40, 0x40], // _
0x60: [0x00, 0x01, 0x02, 0x04, 0x00], // `
0x61: [0x20, 0x54, 0x54, 0x54, 0x78], // a
0x62: [0x7F, 0x48, 0x44, 0x44, 0x38], // b
0x63: [0x38, 0x44, 0x44, 0x44, 0x20], // c
0x64: [0x38, 0x44, 0x44, 0x48, 0x7F], // d
0x65: [0x38, 0x54, 0x54, 0x54, 0x18], // e
0x66: [0x08, 0x7E, 0x09, 0x01, 0x02], // f
0x67: [0x0C, 0x52, 0x52, 0x52, 0x3E], // g
0x68: [0x7F, 0x08, 0x04, 0x04, 0x78], // h
0x69: [0x00, 0x44, 0x7D, 0x40, 0x00], // i
0x6A: [0x20, 0x40, 0x44, 0x3D, 0x00], // j
0x6B: [0x7F, 0x10, 0x28, 0x44, 0x00], // k
0x6C: [0x00, 0x41, 0x7F, 0x40, 0x00], // l
0x6D: [0x7C, 0x04, 0x18, 0x04, 0x78], // m
0x6E: [0x7C, 0x08, 0x04, 0x04, 0x78], // n
0x6F: [0x38, 0x44, 0x44, 0x44, 0x38], // o
0x70: [0x7C, 0x14, 0x14, 0x14, 0x08], // p
0x71: [0x08, 0x14, 0x14, 0x18, 0x7C], // q
0x72: [0x7C, 0x08, 0x04, 0x04, 0x08], // r
0x73: [0x48, 0x54, 0x54, 0x54, 0x20], // s
0x74: [0x04, 0x3F, 0x44, 0x40, 0x20], // t
0x75: [0x3C, 0x40, 0x40, 0x20, 0x7C], // u
0x76: [0x1C, 0x20, 0x40, 0x20, 0x1C], // v
0x77: [0x3C, 0x40, 0x30, 0x40, 0x3C], // w
0x78: [0x44, 0x28, 0x10, 0x28, 0x44], // x
0x79: [0x0C, 0x50, 0x50, 0x50, 0x3C], // y
0x7A: [0x44, 0x64, 0x54, 0x4C, 0x44], // z
0x7B: [0x00, 0x08, 0x36, 0x41, 0x00], // {
0x7C: [0x00, 0x00, 0x7F, 0x00, 0x00], // |
0x7D: [0x00, 0x41, 0x36, 0x08, 0x00], // }
0x7E: [0x10, 0x08, 0x08, 0x10, 0x08], // ~
};
// Built-in font metrics (5×7, 1px column gap → advance = 6)
export const GLYPH_W = 5;
export const GLYPH_H = 7;
export const GLYPH_ADVANCE = 6; // width + 1px gap
/**
* Return column bytes for a codepoint from the built-in 5×7 font.
* Falls back to '?' (0x3F) for unknown codepoints.
*/
export function builtinGlyph(cp: number): Uint8Array {
const cols = BUILTIN_FONT_DATA[cp] ?? BUILTIN_FONT_DATA[0x3F]!;
return new Uint8Array(cols);
}
const HDR_SIZE = 23;
// ---------------------------------------------------------------------------
// U8G2 binary font parser
// BitReader — LSB-first, matching u8g2_font_decode_get_unsigned_bits exactly:
// val = *ptr >> bit_pos
// if (bit_pos + cnt >= 8): val |= *(ptr+1) << (8 - bit_pos); ptr++
// val &= (1 << cnt) - 1
// ---------------------------------------------------------------------------
/** Bit-level reader over a Uint8Array, MSB-first within each byte. */
class BitReader {
private pos = 0; // bit position
constructor(private data: Uint8Array, startBit = 0) {
this.pos = startBit;
private ptr: number;
private bitPos: number;
constructor(private data: Uint8Array, startByte: number) {
this.ptr = startByte;
this.bitPos = 0;
}
read(n: number): number {
let v = 0;
for (let i = 0; i < n; i++) {
const byteIdx = (this.pos) >> 3;
const bitIdx = 7 - ((this.pos) & 7); // MSB first
v = (v << 1) | ((this.data[byteIdx]! >> bitIdx) & 1);
this.pos++;
read(cnt: number): number {
let val = (this.data[this.ptr]! >> this.bitPos) & 0xFF;
const newPos = this.bitPos + cnt;
if (newPos >= 8) {
this.ptr++;
val |= (this.data[this.ptr]! << (8 - this.bitPos));
this.bitPos = newPos - 8;
} else {
this.bitPos = newPos;
}
return v;
return val & ((1 << cnt) - 1);
}
/** Read a signed value stored in `n` bits (two's complement). */
readSigned(n: number): number {
const v = this.read(n);
const sign = 1 << (n - 1);
return v & sign ? v - (sign << 1) : v;
// Offset-binary signed: stored as unsigned + (1 << (cnt-1)), so decode = val - (1 << (cnt-1))
// Matches: v = get_unsigned(cnt); d = 1 << (cnt-1); v -= d;
readSigned(cnt: number): number {
return this.read(cnt) - (1 << (cnt - 1));
}
get bitPos(): number { return this.pos; }
}
/** Read a big-endian uint16 from a byte array at `offset`. */
function readU16BE(data: Uint8Array, offset: number): number {
return ((data[offset]! << 8) | data[offset + 1]!) >>> 0;
function readU16BE(data: Uint8Array, off: number): number {
return ((data[off]! << 8) | data[off + 1]!) >>> 0;
}
/**
* Glyph result from a U8G2 font blob.
* `cols` column bytes (bit 0 = top row), length = w.
*/
export interface U8G2GlyphData {
cols: Uint8Array;
w: number;
h: number;
yOff: number; // positive = shift down
}
/**
* Parse header fields from a U8G2 font blob and return the key metrics
* needed to decode glyphs.
*/
// ---------------------------------------------------------------------------
// Font header (23 bytes)
// Offsets 17/18, 19/20, 21/22 are relative to end of header (byte 23)
// ---------------------------------------------------------------------------
function parseHeader(font: Uint8Array) {
return {
glyphCnt: font[0]!,
bbxMode: font[1]!,
bitsPerRle0: font[2]!,
bitsPerRle1: font[3]!,
m0: font[2]!,
m1: font[3]!,
bitsPerW: font[4]!,
bitsPerH: font[5]!,
bitsPerX: font[6]!,
bitsPerY: font[7]!,
bitsPerDx: font[8]!,
maxCharW: font[9]!,
maxCharH: font[10]!,
xOff: font[11]! as number, // signed but treated as-is
yOff: font[12]! as number,
ascentA: font[13]!,
descentG: font[14]!,
startUpper: readU16BE(font, 17),
startLower: readU16BE(font, 19),
startUnicode: readU16BE(font, 21),
ascentA: font[13]! as number,
// offsets stored as BE uint16, relative to end of 23-byte header
startUpper: HDR_SIZE + readU16BE(font, 17),
startLower: HDR_SIZE + readU16BE(font, 19),
startUnicode: HDR_SIZE + readU16BE(font, 21),
};
}
/**
* Decode one glyph from the U8G2 font RLE bitstream at `byteOffset`.
* Returns decoded data or null if the stream is malformed.
*
* U8G2 glyph encoding (bitstream, MSB-first):
* [bitsPerW] w glyph bitmap width
* [bitsPerH] h glyph bitmap height
* [bitsPerX] x signed x bearing
* [bitsPerY] y signed y bearing (positive = up from baseline)
* [bitsPerDx] dwidth horizontal advance
* Then w×h bits of RLE-encoded bitmap:
* repeat:
* 1-bit type (0 = off-run, 1 = on-run)
* bitsPerRleN count bits
*/
// ---------------------------------------------------------------------------
// RLE decoder — matches fd_decode / u8g2_font_decode_glyph in the C source.
//
// The bitstream is a sequence of (zeros-run, ones-run) pairs, repeated.
// Each pair:
// [m0 bits] count of zero pixels
// [m1 bits] count of one pixels
// [unary] repetition: read 1-bits until a 0-stop; reps = (number of 1s) + 1
//
// This matches bg_rle_compress which encodes pairs and then a unary repeat.
// ---------------------------------------------------------------------------
function decodeRLE(
br: BitReader,
w: number,
h: number,
m0: number,
m1: number,
): Uint8Array {
const total = w * h;
const bits = new Uint8Array(total);
let filled = 0;
while (filled < total) {
const zeros = br.read(m0);
const ones = br.read(m1);
let reps = 1;
while (br.read(1) === 1) reps++;
for (let r = 0; r < reps && filled < total; r++) {
for (let p = 0; p < zeros && filled < total; p++) bits[filled++] = 0;
for (let p = 0; p < ones && filled < total; p++) bits[filled++] = 1;
}
}
return bits;
}
// ---------------------------------------------------------------------------
// Glyph result
// ---------------------------------------------------------------------------
export interface U8G2GlyphData {
cols: Uint8Array; // column bytes (bit 0 = top row), length = w
w: number;
h: number;
dx: number; // horizontal advance
yOff: number; // downward offset from top of cell to top of glyph
}
// ---------------------------------------------------------------------------
// Decode the glyph bitstream at byteOffset (after the record header bytes).
// Field order from C source (u8g2_font.c lines 590–625):
// glyph_width, glyph_height, x (signed), y (signed), delta_x (signed)
// ---------------------------------------------------------------------------
function decodeGlyphAt(
font: Uint8Array,
byteOffset: number,
hdr: ReturnType<typeof parseHeader>,
): U8G2GlyphData | null {
try {
const br = new BitReader(font, byteOffset * 8);
const w = br.read(hdr.bitsPerW);
const h = br.read(hdr.bitsPerH);
const xBrg = br.readSigned(hdr.bitsPerX);
const yBrg = br.readSigned(hdr.bitsPerY); // positive = up
br.read(hdr.bitsPerDx); // advance – not needed here
if (w === 0 || h === 0) {
// Blank glyph (e.g. space)
return { cols: new Uint8Array(GLYPH_W), w: GLYPH_W, h: GLYPH_H, yOff: 0 };
}
): U8G2GlyphData {
const br = new BitReader(font, byteOffset);
const w = br.read(hdr.bitsPerW);
const h = br.read(hdr.bitsPerH);
/* x */ br.readSigned(hdr.bitsPerX); // x bearing — consumed but not needed
const yB = br.readSigned(hdr.bitsPerY);
const dx = br.readSigned(hdr.bitsPerDx);
if (w === 0 || h === 0) {
// Blank glyph (e.g. space) — advance is stored in dx
return { cols: new Uint8Array(1), w: 1, h: 0, dx: Math.max(dx, 1), yOff: 0 };
}
// Decode RLE bitmap into a flat bit array [row-major, top-to-bottom]
const totalBits = w * h;
const bits = new Uint8Array(totalBits);
let filled = 0;
while (filled < totalBits) {
const type = br.read(1); // 0=off, 1=on
const bitsN = type === 0 ? hdr.bitsPerRle0 : hdr.bitsPerRle1;
const count = br.read(bitsN) + 1; // stored as count-1
const val = type & 1;
for (let i = 0; i < count && filled < totalBits; i++) {
bits[filled++] = val;
}
}
// Decode row-major horizontal bitmap
const bits = decodeRLE(br, w, h, hdr.m0, hdr.m1);
// Convert row-major bitmap → column bytes (bit 0 = top row) to match
// the builtin font layout consumed by rasterizeText.
const cols = new Uint8Array(w);
for (let row = 0; row < h; row++) {
for (let col = 0; col < w; col++) {
if (bits[row * w + col]) {
// ts-ignore
cols[col] |= 1 << row;
}
}
// Convert row-major → column bytes (bit 0 = top row) for rasterizeText
const cols = new Uint8Array(w);
for (let row = 0; row < h; row++) {
for (let col = 0; col < w; col++) {
if (bits[row * w + col]) cols[col] |= (1 << row);
}
}
// yBrg is measured upward from baseline in U8G2 conventions.
// Convert to a downward pixel offset relative to the top of GLYPH_H.
// ascentA = pixels above baseline for capital 'A'
// yOff = (ascentA - yBrg - h): rows to push down from the top of
// the glyph box so the baseline lands in the right place.
const yOff = Math.max(0, hdr.ascentA - yBrg - h);
// yB: signed distance from baseline upward to bottom of glyph bounding box.
// ascentA: pixels above baseline for cap-height reference.
// yOff: distance downward from top of the notional box to top of this glyph.
const yOff = hdr.ascentA - yB - h;
return { cols, w, h, yOff };
} catch {
return null;
}
return { cols, w, h, dx, yOff };
}
/**
* Scan the correct block of a U8G2 font for `cp` and return decoded glyph
* data, or null if not found.
*
* Each glyph record in the jump table starts with:
* [1 byte] encoding (codepoint, 0x000xFF in ASCII blocks)
* [1 byte] jump offset to next record (0 = end of block)
* then the bitstream described in decodeGlyphAt.
*
* For the Unicode block the encoding is 2 bytes (uint16 LE) and the jump
* offset is also 2 bytes (uint16 LE).
*/
// ---------------------------------------------------------------------------
// Glyph search
//
// Record layout (cp < 0x0100):
// [1 byte] encoding
// [1 byte] jump = byte distance from START of this record to next record
// [bitstream …]
//
// Record layout (cp >= 0x0100):
// [2 bytes BE] encoding
// [1 byte] jump (same semantics)
// [bitstream …]
//
// Unicode section is preceded by a lookup table (added in v2.23):
// pairs of [uint16 BE delta | uint16 BE last-encoding], terminated by last==0xFFFF
// delta is cumulative byte offset from TABLE START to the glyph sub-block.
// ---------------------------------------------------------------------------
export function u8g2Glyph(cp: number, font: Uint8Array): U8G2GlyphData | null {
if (font.length < 23) return null;
if (font.length < HDR_SIZE) return null;
const hdr = parseHeader(font);
let blockStart: number;
let wide: boolean; // true for the Unicode block (2-byte encoding + 2-byte jump)
if (cp >= 0x20 && cp <= 0x5F) {
blockStart = hdr.startUpper;
wide = false;
} else if (cp >= 0x60 && cp <= 0x9F) {
blockStart = hdr.startLower;
wide = false;
} else {
blockStart = hdr.startUnicode;
wide = true;
}
if (blockStart === 0 || blockStart >= font.length) return null;
let pos = blockStart;
try {
if (cp >= 0x0100) {
// ── Unicode block ────────────────────────────────────────────────────
const tableBase = hdr.startUnicode;
if (tableBase + 4 > font.length) return null;
// Walk the jump table (BE uint16 pairs) to find the right sub-block.
// delta is offset from tableBase to start of sub-block.
let glyphStart = tableBase;
let pos = tableBase;
while (pos + 4 <= font.length) {
const delta = readU16BE(font, pos);
const lastEncoding = readU16BE(font, pos + 2);
pos += 4;
if (lastEncoding === 0xFFFF) {
glyphStart = tableBase + delta;
break;
}
if (lastEncoding >= cp) {
glyphStart = tableBase + delta;
break;
}
}
while (pos < font.length) {
let encoding: number;
let jump: number;
// Scan glyph records in the sub-block
let recPos = glyphStart;
while (recPos + 3 <= font.length) {
const recStart = recPos;
const encoding = readU16BE(font, recPos);
const jump = font[recPos + 2]!;
if (jump === 0) break;
recPos += 3; // bitstream starts here
if (encoding === cp) return decodeGlyphAt(font, recPos, hdr);
recPos = recStart + jump;
}
return null;
if (wide) {
if (pos + 4 > font.length) break;
encoding = (font[pos]! | (font[pos + 1]! << 8)) >>> 0; // LE uint16
jump = (font[pos + 2]! | (font[pos + 3]! << 8)) >>> 0;
pos += 4;
} else {
if (pos + 2 > font.length) break;
encoding = font[pos]!;
jump = font[pos + 1]!;
pos += 2;
}
if (jump === 0) break; // end-of-block sentinel
if (encoding === cp) {
return decodeGlyphAt(font, pos, hdr);
// ── ASCII block (cp < 0x0100) ────────────────────────────────────────
// Jump-start from the nearest block offset to avoid scanning from byte 0.
let recPos: number;
if (cp >= 0x61) recPos = hdr.startLower;
else if (cp >= 0x41) recPos = hdr.startUpper;
else recPos = HDR_SIZE;
while (recPos + 2 <= font.length) {
const recStart = recPos;
const encoding = font[recPos]!;
const jump = font[recPos + 1]!;
if (jump === 0) break;
recPos += 2; // bitstream starts here
if (encoding === cp) return decodeGlyphAt(font, recPos, hdr);
recPos = recStart + jump;
}
return null;
}
pos += jump; // skip to next record
} catch {
return null;
}
return null; // codepoint not in font
}
// ---------------------------------------------------------------------------
// Text → pixel-image rasteriser (as specified)
// Text → pixel-image rasteriser
// ---------------------------------------------------------------------------
export function rasterizeText(
text: string,
cellHeight: number,
font?: MonoDisplayFont,
font: { FONT_DATA: Uint8Array },
): { pixels: Uint8Array; width: number; height: number } {
const glyphs: Array<{ cols: Uint8Array; w: number; h: number; yOff: number }> = [];
const hdr = parseHeader(font.FONT_DATA);
const glyphs: Array<U8G2GlyphData> = [];
for (const char of text) {
const cp = char.codePointAt(0) ?? 0x3F;
const custom = font ? u8g2Glyph(cp, font.FONT_DATA) : null;
if (custom) {
glyphs.push({ cols: custom.cols, w: custom.w, h: custom.h, yOff: custom.yOff });
} else {
glyphs.push({ cols: builtinGlyph(cp), w: GLYPH_W, h: GLYPH_H, yOff: 0 });
}
const g = u8g2Glyph(cp, font.FONT_DATA);
if (g) glyphs.push(g);
}
if (glyphs.length === 0) {
return { pixels: new Uint8Array(cellHeight), width: 1, height: cellHeight };
}
const totalW = Math.max(1, glyphs.length * GLYPH_ADVANCE - 1);
// Total width: sum all advances, but last glyph uses its actual pixel width
const totalW = glyphs.reduce((sum, g, i) =>
sum + (i < glyphs.length - 1 ? g.dx : g.w), 0);
const pixels = new Uint8Array(totalW * cellHeight);
const yBase = Math.max(0, Math.floor((cellHeight - GLYPH_H) / 2));
let x = 0;
// Centre the full font (ascent + |descent|) vertically in the cell.
// header byte 14 is descent of 'g' — stored as a negative signed value.
const descent = font.FONT_DATA[14]! > 127 ? font.FONT_DATA[14]! - 256 : font.FONT_DATA[14]!;
const fontH = hdr.ascentA - descent; // total font height in pixels
const topPad = Math.floor((cellHeight - fontH) / 2);
const baseline = topPad + hdr.ascentA;
let x = 0;
for (const g of glyphs) {
const top = yBase + g.yOff;
// yOff = ascentA - yBearing - h (may be negative if glyph taller than 'A')
const top = baseline - hdr.ascentA + g.yOff;
for (let col = 0; col < g.w; col++) {
const colByte = g.cols[col] ?? 0;
for (let row = 0; row < g.h; row++) {
@ -404,7 +286,7 @@ export function rasterizeText(
}
}
}
x += GLYPH_ADVANCE;
x += g.dx;
}
return { pixels, width: totalW, height: cellHeight };

@ -16,7 +16,7 @@ import {
type MonoFormatVScroll
} from "./types.js";
import { type MonoDisplayDriverOptions } from "./driver";
import { rasterizeText, MonoDisplayFont } from "./font";
import { rasterizeText, MonoDisplayFont, u8g2_font_5x7_tf } from "./font";
/**
* Renders a MonoFormatFile onto an HTMLCanvasElement.
* Uses setInterval at 1000/fps ms per tick. All element state (animation
@ -46,7 +46,7 @@ export class MonoDisplayRenderer {
this.ctx = ctx;
this.opts = opts;
this.builtinFonts = [
u8g2_font_5x7_tf,
];
}

@ -11,27 +11,27 @@ export enum FileVersion {
/** Illegal - must not appear in a valid file */
Illegal = 0,
/** Current specification version */
V1 = 1,
V1 = 1,
}
export enum SectionType {
/** List of drawn elements - always shown */
ElementsAlways = 1,
ElementsAlways = 1,
/** List of drawn elements - shown only within a POSIX timestamp window */
ElementsTimespan = 2,
/** U8G2-format custom font used by draw elements in this file */
CustomFont = 32,
CustomFont = 32,
}
export enum ElementType {
Image2D = 1,
Animation = 2,
Image2D = 1,
Animation = 2,
HorizontalScroll = 3,
VerticalScroll = 4,
Line = 5,
ClippedText = 16,
HScrollText = 17,
CurrentTime = 18,
VerticalScroll = 4,
Line = 5,
ClippedText = 16,
HScrollText = 17,
CurrentTime = 32,
}
// ---------------------------------------------------------------------------
@ -41,9 +41,9 @@ export enum ElementType {
/** Bit-field flags for ElementsAlways / ElementsTimespan sections. */
export interface SectionFlags {
/** Bit 0 - render elements onto the front-side buffer. */
drawFront?: boolean;
drawFront?: boolean;
/** Bit 1 - render elements onto the back-side buffer. */
drawBack?: boolean;
drawBack?: boolean;
/** Bit 2 - clear targeted buffer(s) before drawing this section's elements. */
clearBuffer?: boolean;
// Bits 3-15: reserved; writers MUST write 0.
@ -55,13 +55,13 @@ export interface SectionFlags {
export interface ScrollElementFlags {
/** Bit 0 - wrap content endlessly. */
endless?: boolean;
endless?: boolean;
/** Bit 1 - reverse scroll direction. */
invertDirection?: boolean;
/** Bit 2 - pad at start edge (left for H-scroll, top for V-scroll). */
padStart?: boolean;
padStart?: boolean;
/** Bit 3 - pad at end edge (right for H-scroll, bottom for V-scroll). */
padEnd?: boolean;
padEnd?: boolean;
// Bits 4-7: reserved.
}
@ -71,9 +71,9 @@ export interface ScrollElementFlags {
export interface CurrentTimeFlags {
/** Bit 0 - use 12-hour clock (default 24-hour). */
clock12h?: boolean;
clock12h?: boolean;
/** Bit 1 - show hours. */
showHours?: boolean;
showHours?: boolean;
/** Bit 2 - show minutes. */
showMinutes?: boolean;
/** Bit 3 - show seconds. */
@ -123,11 +123,11 @@ export function customFontOrdinal(fontIndex: FontIndex): number {
// ---------------------------------------------------------------------------
export interface Image2DElement {
type: ElementType.Image2D;
type: ElementType.Image2D;
/** Row-major, 1 byte/pixel (0 = off, 1 = on). Packed to 1bpp on write. */
pixels: Uint8Array;
width: number;
height: number;
pixels: Uint8Array;
width: number;
height: number;
xOffset?: number; // default 0
yOffset?: number; // default 0
}
@ -138,10 +138,10 @@ export interface AnimationFrameDescriptor {
}
export interface AnimationElement {
type: ElementType.Animation;
width: number;
height: number;
frames: AnimationFrameDescriptor[];
type: ElementType.Animation;
width: number;
height: number;
frames: AnimationFrameDescriptor[];
/**
* Advance frame every (updateInterval + 1) ticks.
* 0 = every tick, 1 = every 2nd tick, ..., 65535 = every 65536th tick.
@ -153,86 +153,86 @@ export interface AnimationElement {
}
export interface HScrollElement {
type: ElementType.HorizontalScroll;
type: ElementType.HorizontalScroll;
/** Viewport width in pixels. */
width: number;
width: number;
/** Viewport height in pixels. */
height: number;
height: number;
/** Scrolling content pixels (contentWidth × height), 1 byte/pixel. */
pixels: Uint8Array;
pixels: Uint8Array;
contentWidth: number;
/**
* Scroll speed byte SS; moves (SS + 1) / 16 pixels per tick.
* Range 0-255. Default 0 (= 1/16 px/tick).
*/
scrollSpeed?: number;
flags?: ScrollElementFlags;
flags?: ScrollElementFlags;
xOffset?: number;
yOffset?: number;
}
export interface VScrollElement {
type: ElementType.VerticalScroll;
type: ElementType.VerticalScroll;
/** Viewport width in pixels. */
width: number;
width: number;
/** Viewport height in pixels. */
height: number;
height: number;
/** Scrolling content pixels (width × contentHeight), 1 byte/pixel. */
pixels: Uint8Array;
pixels: Uint8Array;
contentHeight: number;
/** Scroll speed byte SS; moves (SS + 1) / 16 pixels per tick. Default 0. */
scrollSpeed?: number;
flags?: ScrollElementFlags;
scrollSpeed?: number;
flags?: ScrollElementFlags;
xOffset?: number;
yOffset?: number;
}
export interface LineElement {
type: ElementType.Line;
xOrigin: number;
yOrigin: number;
xTarget: number;
yTarget: number;
type: ElementType.Line;
xOrigin: number;
yOrigin: number;
xTarget: number;
yTarget: number;
/** Reserved by spec; writers MUST write 0. */
lineStyle?: number;
lineStyle?: number;
invertPixels?: boolean; // flags bit 0
}
export interface ClippedTextElement {
type: ElementType.ClippedText;
text: string; // UTF-8
width: number;
height: number;
type: ElementType.ClippedText;
text: string; // UTF-8
width: number;
height: number;
/** 0-32767 = built-in font; 0x8000+ = custom font in file. Default 0. */
fontIndex?: FontIndex;
xOffset?: number;
yOffset?: number;
xOffset?: number;
yOffset?: number;
}
export interface HScrollTextElement {
type: ElementType.HScrollText;
text: string; // UTF-8
width: number;
height: number;
type: ElementType.HScrollText;
text: string; // UTF-8
width: number;
height: number;
scrollSpeed?: number; // SS byte, default 0
fontIndex?: FontIndex;
flags?: ScrollElementFlags;
fontIndex?: FontIndex;
flags?: ScrollElementFlags;
xOffset?: number;
yOffset?: number;
}
export interface CurrentTimeElement {
type: ElementType.CurrentTime;
width: number;
width: number;
height: number;
/** 0-32767 = built-in font; 0x8000+ = custom font in file. Default 0. */
fontIndex?: FontIndex;
fontIndex?: FontIndex;
/**
* UTC offset in whole minutes (signed 16-bit integer).
* e.g. UTC+5:30 330, UTC-8 -480.
*/
utcOffsetMinutes?: number;
flags?: CurrentTimeFlags;
flags?: CurrentTimeFlags;
xOffset?: number;
yOffset?: number;
}
@ -252,17 +252,17 @@ export type DrawElement =
// ---------------------------------------------------------------------------
export interface ElementsAlwaysDescriptor {
flags?: SectionFlags;
flags?: SectionFlags;
elements: DrawElement[];
}
export interface ElementsTimespanDescriptor {
flags?: SectionFlags;
elements: DrawElement[];
flags?: SectionFlags;
elements: DrawElement[];
/** POSIX timestamp (seconds) - section visible when now >= startTimestamp. */
startTimestamp: bigint;
startTimestamp: bigint;
/** POSIX timestamp (seconds) - section visible when now < endTimestamp. */
endTimestamp: bigint;
endTimestamp: bigint;
}
export interface CustomFontDescriptor {
@ -293,107 +293,107 @@ export interface MonoDisplayFileDescriptor {
export interface MonoFormatPixelImage {
/** Unpacked: 1 byte per pixel, 0 = off, 1 = on, row-major. */
pixels: Uint8Array;
width: number;
width: number;
height: number;
}
export interface MonoFormatImage2D {
type: ElementType.Image2D;
type: ElementType.Image2D;
xOffset: number;
yOffset: number;
image: MonoFormatPixelImage;
image: MonoFormatPixelImage;
}
export interface MonoFormatAnimation {
type: ElementType.Animation;
xOffset: number;
yOffset: number;
width: number;
height: number;
type: ElementType.Animation;
xOffset: number;
yOffset: number;
width: number;
height: number;
updateInterval: number;
frames: MonoFormatPixelImage[];
frames: MonoFormatPixelImage[];
}
export interface MonoFormatScrollFlags {
endless: boolean;
endless: boolean;
invertDirection: boolean;
padStart: boolean;
padEnd: boolean;
padStart: boolean;
padEnd: boolean;
}
export interface MonoFormatHScroll {
type: ElementType.HorizontalScroll;
xOffset: number;
yOffset: number;
width: number;
height: number;
type: ElementType.HorizontalScroll;
xOffset: number;
yOffset: number;
width: number;
height: number;
contentWidth: number;
scrollSpeed: number;
flags: MonoFormatScrollFlags;
content: MonoFormatPixelImage;
scrollSpeed: number;
flags: MonoFormatScrollFlags;
content: MonoFormatPixelImage;
}
export interface MonoFormatVScroll {
type: ElementType.VerticalScroll;
xOffset: number;
yOffset: number;
width: number;
height: number;
type: ElementType.VerticalScroll;
xOffset: number;
yOffset: number;
width: number;
height: number;
contentHeight: number;
scrollSpeed: number;
flags: MonoFormatScrollFlags;
content: MonoFormatPixelImage;
scrollSpeed: number;
flags: MonoFormatScrollFlags;
content: MonoFormatPixelImage;
}
export interface MonoFormatLine {
type: ElementType.Line;
xOrigin: number;
yOrigin: number;
xTarget: number;
yTarget: number;
lineStyle: number;
type: ElementType.Line;
xOrigin: number;
yOrigin: number;
xTarget: number;
yTarget: number;
lineStyle: number;
invertPixels: boolean;
}
export interface MonoFormatClippedText {
type: ElementType.ClippedText;
xOffset: number;
yOffset: number;
width: number;
height: number;
type: ElementType.ClippedText;
xOffset: number;
yOffset: number;
width: number;
height: number;
fontIndex: FontIndex;
text: string;
text: string;
}
export interface MonoFormatHScrollText {
type: ElementType.HScrollText;
xOffset: number;
yOffset: number;
width: number;
height: number;
type: ElementType.HScrollText;
xOffset: number;
yOffset: number;
width: number;
height: number;
scrollSpeed: number;
fontIndex: FontIndex;
flags: MonoFormatScrollFlags;
text: string;
fontIndex: FontIndex;
flags: MonoFormatScrollFlags;
text: string;
}
export interface MonoFormatCurrentTimeFlags {
clock12h: boolean;
showHours: boolean;
clock12h: boolean;
showHours: boolean;
showMinutes: boolean;
showSeconds: boolean;
}
export interface MonoFormatCurrentTime {
type: ElementType.CurrentTime;
xOffset: number;
yOffset: number;
width: number;
height: number;
fontIndex: FontIndex;
type: ElementType.CurrentTime;
xOffset: number;
yOffset: number;
width: number;
height: number;
fontIndex: FontIndex;
/** UTC offset in minutes (signed). */
utcOffsetMinutes: number;
flags: MonoFormatCurrentTimeFlags;
flags: MonoFormatCurrentTimeFlags;
}
export type MonoFormatElement =
@ -407,29 +407,29 @@ export type MonoFormatElement =
| MonoFormatCurrentTime;
export interface MonoFormatSectionFlags {
drawFront: boolean;
drawBack: boolean;
drawFront: boolean;
drawBack: boolean;
clearBuffer: boolean;
}
export interface MonoFormatElementsAlways {
sectionType: SectionType.ElementsAlways;
flags: MonoFormatSectionFlags;
elements: MonoFormatElement[];
flags: MonoFormatSectionFlags;
elements: MonoFormatElement[];
}
export interface MonoFormatElementsTimespan {
sectionType: SectionType.ElementsTimespan;
flags: MonoFormatSectionFlags;
sectionType: SectionType.ElementsTimespan;
flags: MonoFormatSectionFlags;
startTimestamp: bigint; // POSIX seconds
endTimestamp: bigint;
elements: MonoFormatElement[];
endTimestamp: bigint;
elements: MonoFormatElement[];
}
export interface MonoFormatCustomFont {
sectionType: SectionType.CustomFont;
/** Raw U8G2 font data (actual bytes, not padded). */
fontData: Uint8Array;
fontData: Uint8Array;
}
export type MonoFormatSection =

Loading…
Cancel
Save