mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2025-12-18 15:17:42 +03:00
Build and use 1-bit font, saves a good amount of space
This commit is contained in:
parent
eceffaa289
commit
79294f6b8f
@ -6,13 +6,13 @@
|
|||||||
|
|
||||||
/// Font data stored PER GLYPH
|
/// Font data stored PER GLYPH
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t width; ///< Bitmap dimensions in pixels
|
uint8_t width; ///< Bitmap dimensions in pixels
|
||||||
uint8_t height; ///< Bitmap dimensions in pixels
|
uint8_t height; ///< Bitmap dimensions in pixels
|
||||||
uint8_t advanceX; ///< Distance to advance cursor (x axis)
|
uint8_t advanceX; ///< Distance to advance cursor (x axis)
|
||||||
int16_t left; ///< X dist from cursor pos to UL corner
|
int16_t left; ///< X dist from cursor pos to UL corner
|
||||||
int16_t top; ///< Y dist from cursor pos to UL corner
|
int16_t top; ///< Y dist from cursor pos to UL corner
|
||||||
uint16_t compressedSize; ///< Size of the zlib-compressed font data.
|
uint16_t dataLength; ///< Size of the font data.
|
||||||
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap
|
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap
|
||||||
} EpdGlyph;
|
} EpdGlyph;
|
||||||
|
|
||||||
/// Glyph interval structure
|
/// Glyph interval structure
|
||||||
@ -28,7 +28,6 @@ typedef struct {
|
|||||||
const EpdGlyph* glyph; ///< Glyph array
|
const EpdGlyph* glyph; ///< Glyph array
|
||||||
const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font
|
const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font
|
||||||
uint32_t intervalCount; ///< Number of unicode intervals.
|
uint32_t intervalCount; ///< Number of unicode intervals.
|
||||||
bool compressed; ///< Does this font use compressed glyph bitmaps?
|
|
||||||
uint8_t advanceY; ///< Newline distance (y axis)
|
uint8_t advanceY; ///< Newline distance (y axis)
|
||||||
int ascender; ///< Maximal height of a glyph above the base line
|
int ascender; ///< Maximal height of a glyph above the base line
|
||||||
int descender; ///< Maximal height of a glyph below the base line
|
int descender; ///< Maximal height of a glyph below the base line
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -7,20 +7,18 @@ import math
|
|||||||
import argparse
|
import argparse
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
# From: https://github.com/vroland/epdiy
|
# Originally from https://github.com/vroland/epdiy
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.")
|
parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.")
|
||||||
parser.add_argument("name", action="store", help="name of the font.")
|
parser.add_argument("name", action="store", help="name of the font.")
|
||||||
parser.add_argument("size", type=int, help="font size to use.")
|
parser.add_argument("size", type=int, help="font size to use.")
|
||||||
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.")
|
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.")
|
||||||
parser.add_argument("--compress", dest="compress", action="store_true", help="compress glyph bitmaps.")
|
|
||||||
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
|
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "compressed_size", "data_offset", "code_point"])
|
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"])
|
||||||
|
|
||||||
font_stack = [freetype.Face(f) for f in args.fontstack]
|
font_stack = [freetype.Face(f) for f in args.fontstack]
|
||||||
compress = args.compress
|
|
||||||
size = args.size
|
size = args.size
|
||||||
font_name = args.name
|
font_name = args.name
|
||||||
|
|
||||||
@ -148,19 +146,18 @@ for i_start, i_end in unvalidated_intervals:
|
|||||||
intervals.append((start, i_end))
|
intervals.append((start, i_end))
|
||||||
|
|
||||||
for face in font_stack:
|
for face in font_stack:
|
||||||
# shift by 6 bytes, because sizes are given as 6-bit fractions
|
|
||||||
# the display has about 150 dpi.
|
|
||||||
face.set_char_size(size << 6, size << 6, 150, 150)
|
face.set_char_size(size << 6, size << 6, 150, 150)
|
||||||
|
|
||||||
total_size = 0
|
total_size = 0
|
||||||
total_packed = 0
|
|
||||||
all_glyphs = []
|
all_glyphs = []
|
||||||
|
|
||||||
for i_start, i_end in intervals:
|
for i_start, i_end in intervals:
|
||||||
for code_point in range(i_start, i_end + 1):
|
for code_point in range(i_start, i_end + 1):
|
||||||
face = load_glyph(code_point)
|
face = load_glyph(code_point)
|
||||||
bitmap = face.glyph.bitmap
|
bitmap = face.glyph.bitmap
|
||||||
pixels = []
|
|
||||||
|
# Build out 4-bit greyscale bitmap
|
||||||
|
pixels4g = []
|
||||||
px = 0
|
px = 0
|
||||||
for i, v in enumerate(bitmap.buffer):
|
for i, v in enumerate(bitmap.buffer):
|
||||||
y = i / bitmap.width
|
y = i / bitmap.width
|
||||||
@ -169,31 +166,49 @@ for i_start, i_end in intervals:
|
|||||||
px = (v >> 4)
|
px = (v >> 4)
|
||||||
else:
|
else:
|
||||||
px = px | (v & 0xF0)
|
px = px | (v & 0xF0)
|
||||||
pixels.append(px);
|
pixels4g.append(px);
|
||||||
px = 0
|
px = 0
|
||||||
# eol
|
# eol
|
||||||
if x == bitmap.width - 1 and bitmap.width % 2 > 0:
|
if x == bitmap.width - 1 and bitmap.width % 2 > 0:
|
||||||
pixels.append(px)
|
pixels4g.append(px)
|
||||||
px = 0
|
px = 0
|
||||||
|
|
||||||
packed = bytes(pixels);
|
# Downsample to 1-bit bitmap - treat any non-zero as black
|
||||||
total_packed += len(packed)
|
pixelsbw = []
|
||||||
compressed = packed
|
px = 0
|
||||||
if compress:
|
pitch = (bitmap.width // 2) + (bitmap.width % 2)
|
||||||
compressed = zlib.compress(packed)
|
for localY in range(bitmap.rows):
|
||||||
|
for xx in range(bitmap.width):
|
||||||
|
px = px << 1
|
||||||
|
bm = pixels4g[localY * pitch + (xx // 2)]
|
||||||
|
if (xx & 1) == 0:
|
||||||
|
if bm & 0xF > 0:
|
||||||
|
px += 1
|
||||||
|
else:
|
||||||
|
if bm & 0xF0 > 0:
|
||||||
|
px += 1
|
||||||
|
|
||||||
|
if (localY * bitmap.width + xx) % 8 == 7:
|
||||||
|
pixelsbw.append(px)
|
||||||
|
px = 0
|
||||||
|
if (bitmap.width * bitmap.rows) % 8 != 0:
|
||||||
|
pixelsbw.append(px)
|
||||||
|
|
||||||
|
|
||||||
|
# Build output data
|
||||||
|
packed = bytes(pixelsbw)
|
||||||
glyph = GlyphProps(
|
glyph = GlyphProps(
|
||||||
width = bitmap.width,
|
width = bitmap.width,
|
||||||
height = bitmap.rows,
|
height = bitmap.rows,
|
||||||
advance_x = norm_floor(face.glyph.advance.x),
|
advance_x = norm_floor(face.glyph.advance.x),
|
||||||
left = face.glyph.bitmap_left,
|
left = face.glyph.bitmap_left,
|
||||||
top = face.glyph.bitmap_top,
|
top = face.glyph.bitmap_top,
|
||||||
compressed_size = len(compressed),
|
data_length = len(packed),
|
||||||
data_offset = total_size,
|
data_offset = total_size,
|
||||||
code_point = code_point,
|
code_point = code_point,
|
||||||
)
|
)
|
||||||
total_size += len(compressed)
|
total_size += len(packed)
|
||||||
all_glyphs.append((glyph, compressed))
|
all_glyphs.append((glyph, packed))
|
||||||
|
|
||||||
# pipe seems to be a good heuristic for the "real" descender
|
# pipe seems to be a good heuristic for the "real" descender
|
||||||
face = load_glyph(ord('|'))
|
face = load_glyph(ord('|'))
|
||||||
@ -201,11 +216,11 @@ face = load_glyph(ord('|'))
|
|||||||
glyph_data = []
|
glyph_data = []
|
||||||
glyph_props = []
|
glyph_props = []
|
||||||
for index, glyph in enumerate(all_glyphs):
|
for index, glyph in enumerate(all_glyphs):
|
||||||
props, compressed = glyph
|
props, packed = glyph
|
||||||
glyph_data.extend([b for b in compressed])
|
glyph_data.extend([b for b in packed])
|
||||||
glyph_props.append(props)
|
glyph_props.append(props)
|
||||||
|
|
||||||
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n * compressed: {compress}\n */")
|
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n */")
|
||||||
print("#pragma once")
|
print("#pragma once")
|
||||||
print("#include \"EpdFontData.h\"\n")
|
print("#include \"EpdFontData.h\"\n")
|
||||||
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
|
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
|
||||||
@ -230,7 +245,6 @@ print(f" {font_name}Bitmaps,")
|
|||||||
print(f" {font_name}Glyphs,")
|
print(f" {font_name}Glyphs,")
|
||||||
print(f" {font_name}Intervals,")
|
print(f" {font_name}Intervals,")
|
||||||
print(f" {len(intervals)},")
|
print(f" {len(intervals)},")
|
||||||
print(f" {1 if compress else 0},")
|
|
||||||
print(f" {norm_ceil(face.size.height)},")
|
print(f" {norm_ceil(face.size.height)},")
|
||||||
print(f" {norm_ceil(face.size.ascender)},")
|
print(f" {norm_ceil(face.size.ascender)},")
|
||||||
print(f" {norm_floor(face.size.descender)},")
|
print(f" {norm_floor(face.size.descender)},")
|
||||||
|
|||||||
@ -2,13 +2,10 @@
|
|||||||
#include <EpdFontFamily.h>
|
#include <EpdFontFamily.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
#include <miniz.h>
|
|
||||||
|
|
||||||
inline int min(const int a, const int b) { return a < b ? a : b; }
|
inline int min(const int a, const int b) { return a < b ? a : b; }
|
||||||
inline int max(const int a, const int b) { return a > b ? a : b; }
|
inline int max(const int a, const int b) { return a > b ? a : b; }
|
||||||
|
|
||||||
static tinfl_decompressor decomp;
|
|
||||||
|
|
||||||
template <typename Renderable>
|
template <typename Renderable>
|
||||||
class EpdFontRenderer {
|
class EpdFontRenderer {
|
||||||
Renderable& renderer;
|
Renderable& renderer;
|
||||||
@ -22,22 +19,6 @@ class EpdFontRenderer {
|
|||||||
void renderString(const char* string, int* x, int* y, uint16_t color, EpdFontStyle style = REGULAR);
|
void renderString(const char* string, int* x, int* y, uint16_t color, EpdFontStyle style = REGULAR);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline int uncompress(uint8_t* dest, size_t uncompressedSize, const uint8_t* source, size_t sourceSize) {
|
|
||||||
if (uncompressedSize == 0 || dest == nullptr || sourceSize == 0 || source == nullptr) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
tinfl_init(&decomp);
|
|
||||||
|
|
||||||
// we know everything will fit into the buffer.
|
|
||||||
const tinfl_status decomp_status =
|
|
||||||
tinfl_decompress(&decomp, source, &sourceSize, dest, dest, &uncompressedSize,
|
|
||||||
TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
|
|
||||||
if (decomp_status != TINFL_STATUS_DONE) {
|
|
||||||
return decomp_status;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Renderable>
|
template <typename Renderable>
|
||||||
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const uint16_t color,
|
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const uint16_t color,
|
||||||
const EpdFontStyle style) {
|
const EpdFontStyle style) {
|
||||||
@ -79,50 +60,24 @@ void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const in
|
|||||||
const uint8_t height = glyph->height;
|
const uint8_t height = glyph->height;
|
||||||
const int left = glyph->left;
|
const int left = glyph->left;
|
||||||
|
|
||||||
const int byteWidth = width / 2 + width % 2;
|
|
||||||
const unsigned long bitmapSize = byteWidth * height;
|
|
||||||
const uint8_t* bitmap = nullptr;
|
const uint8_t* bitmap = nullptr;
|
||||||
|
bitmap = &fontFamily->getData(style)->bitmap[offset];
|
||||||
if (fontFamily->getData(style)->compressed) {
|
|
||||||
auto* tmpBitmap = static_cast<uint8_t*>(malloc(bitmapSize));
|
|
||||||
if (tmpBitmap == nullptr && bitmapSize) {
|
|
||||||
Serial.println("Failed to allocate memory for decompression buffer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uncompress(tmpBitmap, bitmapSize, &fontFamily->getData(style)->bitmap[offset], glyph->compressedSize);
|
|
||||||
bitmap = tmpBitmap;
|
|
||||||
} else {
|
|
||||||
bitmap = &fontFamily->getData(style)->bitmap[offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitmap != nullptr) {
|
if (bitmap != nullptr) {
|
||||||
for (int localY = 0; localY < height; localY++) {
|
for (int glyphY = 0; glyphY < height; glyphY++) {
|
||||||
int yy = *y - glyph->top + localY;
|
int screenY = *y - glyph->top + glyphY;
|
||||||
const int startPos = *x + left;
|
for (int glyphX = 0; glyphX < width; glyphX++) {
|
||||||
bool byteComplete = startPos % 2;
|
const int pixelPosition = glyphY * width + glyphX;
|
||||||
int localX = max(0, -startPos);
|
int screenX = *x + left + glyphX;
|
||||||
const int maxX = startPos + width;
|
|
||||||
|
|
||||||
for (int xx = startPos; xx < maxX; xx++) {
|
const uint8_t byte = bitmap[pixelPosition / 8];
|
||||||
uint8_t bm = bitmap[localY * byteWidth + localX / 2];
|
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
||||||
if ((localX & 1) == 0) {
|
|
||||||
bm = bm & 0xF;
|
|
||||||
} else {
|
|
||||||
bm = bm >> 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bm) {
|
if ((byte >> bit_index) & 1) {
|
||||||
renderer.drawPixel(xx, yy, color);
|
renderer.drawPixel(screenX, screenY, color);
|
||||||
}
|
}
|
||||||
byteComplete = !byteComplete;
|
|
||||||
localX++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fontFamily->getData(style)->compressed) {
|
|
||||||
free(const_cast<uint8_t*>(bitmap));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*x += glyph->advanceX;
|
*x += glyph->advanceX;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user