8000 framebuf: Add support for additional fonts in text method by elulis · Pull Request #16470 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

framebuf: Add support for additional fonts in text method #16470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

elulis
Copy link
@elulis elulis commented Dec 22, 2024

Summary

Added an optional font_id parameter to the FrameBuffer.text method. The font_id allows selection among different font styles: 0, 1, 2, and 3. The method now returns the width of the drawn text in pixels. Updated the documentation to reflect the new font options:

  • 0: z1mono8_6x8 — Monospace & Regular
  • 1: z1mono8b_8x8 — Monospace & Bold (replacing the original petme128)
  • 2: z1prop8_6x8 — Proportional & Regular
  • 3: z1prop8b_8x8 — Proportional & Bold

The font_z1fonts.h replaces font_petme128_8x8.h at the same size (8 bytes for each character) and stores a proportional & regular font with additional information for runtime font conversion.


demo

This enhancement allows users to select from four different font styles. The method now also returns the width of the drawn text in pixels, which can be useful for layout calculations. The new fonts were designed using PixelForge and are released under the MIT license.

Testing

I verified the changes by running the tests/extmod/framebuf1.py script on the ports/unix build of MicroPython to ensure forward compatibility. The script and expected outputs were updated accordingly. Additionally, I built the firmware for the ESP32-C6 using ports/esp32 and tested it with a monochrome LCD, confirming that the new font functionality works as expected.

Trade-offs and Alternatives

After adding the compile switch MICROPY_PY_FRAMEBUF_ALL_FONTS which is disabled by default, the firmware space finally reduced to below original size. At this point, the font preview for font_id 0/1/2/3 is as follows (equivalent to only having two fonts, 0/1):

demo2

When configuring the compile switch in mpconfigport.h, the firmware space is expected to consume an additional 300 bytes. At this point, all four fonts are supported as the first demo preview shown.
(edit, the following ports have enabled: ports/unix, ports/rp2, ports/esp32 )

Copy link
codecov bot commented Dec 22, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.59%. Comparing base (1360584) to head (117d23d).
Report is 74 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #16470      +/-   ##
==========================================
+ Coverage   98.57%   98.59%   +0.01%     
==========================================
  Files         164      167       +3     
  Lines       21349    21634     +285     
==========================================
+ Hits        21045    21329     +284     
- Misses        304      305       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
github-actions bot commented Dec 22, 2024

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:  +424 +0.050% standard
      stm32:  -100 -0.026% PYBV10
     mimxrt:   -48 -0.013% TEENSY40
        rp2:  +280 +0.031% RPI_PICO_W
       samd:   -40 -0.015% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    -8 -0.002% VIRT_RV32

@elulis elulis force-pushed the add-fonts branch 2 times, most recently from ccfea6c to d65f680 Compare December 22, 2024 14:47
@robert-hh
Copy link
Contributor

For MCUs with small flash sizes, the code size is hardly acceptable.

@elulis
Copy link
Author
elulis commented Dec 22, 2024

For MCUs with small flash sizes, the code size is hardly acceptable.

Thank you for your feedback, would it be possible to consider adding conditional compilation for this feature?

@robert-hh
< 8000 div class="timeline-comment-actions flex-shrink-0 d-flex flex-items-center">
Copy link
Contributor

Yes, compile switches will help.

@peterhinch
Copy link
Contributor

The maintainers would need evidence that any fonts are copyright-free.

@elulis
Copy link
Author
elulis commented Dec 23, 2024

Yes, compile switches will help.

Thank you for your suggestion. I plan to explore the following options:

  • Plan A: Introduce a compile switch:

    • For ports with limited flash memory, the switch will be disabled by default. This will allow the FrameBuffer.text invokes to run without errors, but it will revert to using the default font (PetMe128).

    • For ports like ESP32 or RP2, which have sufficient flash memory, the switch can be enabled, allowing users to choose between the available fonts.

  • Plan B: Investigate the possibility of redesigning the Z1Prop8b_8x8 font so that a single font can be converted into four different styles at runtime without significant space or time overhead:

    • For the Z1Prop8b_8x8 font, I calculate the width using the last two bytes (columns): width_in_pixel = a[6] == 0 ? a[7] : 8.

          0x04, 0x7d, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x04, // 105=i,4
          0x3c, 0x7c, 0x40, 0x70, 0x40, 0x7c, 0x7c, 0x00, // 119=w,8
    • It might be feasible to design a well-crafted proportional bold font that can:

      • Be used directly as a proportional bold font.

      • Be used with center alignment as a monospace bold font.

      • Skip some columns to function as a regular proportional font (non-bold).

      • Skip some columns and use center alignment to function as a regular monospace font.

    • The most challenging aspect will be the design and determining if the last column/byte can store the necessary conversion information.

@elulis
Copy link
Author
elulis commented Dec 23, 2024

The maintainers would need evidence that any fonts are copyright-free.

Thank you for bringing this to my attention. I would appreciate any guidance on how to provide the necessary proof. I can offer the original .pxf files created using PixelForge as evidence of the design process. My detailed workflow is as follows:

  1. Design and adjust the fonts in PixelForge.
  2. Export the designs in .ttf format.
  3. Convert the .ttf files to .otf using FontForge for better compatibility with the micropython-font-to-py tool.
  4. Use micropython-font-to-py to generate the fonts in Python format.
  5. Finally, convert the Python fonts to C format using a custom script.

I hope this information helps clarify the process and demonstrates the originality of the font designs. Please let me know if there are any additional steps I should take.

@peterhinch
Copy link
Contributor

If you designed the fonts yourself there is no problem with copyright. The problem arises where people use tools to convert fonts published by others. Some fonts are commercial products protected by copyright.

FYI step 3 above is not necessary: font-to-py can handle TTF files.

@elulis
Copy link
Author
elulis commented Dec 25, 2024

If you designed the fonts yourself there is no problem with copyright. The problem arises where people use tools to convert fonts published by others. Some fonts are commercial products protected by copyright.

FYI step 3 above is not necessary: font-to-py can handle TTF files.

Thank you for the information and for sharing micropython-font-to-py. I appreciate it!

@elulis
Copy link
Author
elulis commented Dec 25, 2024

I have developed an approach starts with a regular proportional font (up to 5 columns) and adds additional information(3 bytes to utilize, still within the original 8 columns font bytes) for each column type, allowing conversion into three other font styles: regular & mono, bold & proportional, and bold & mono.

There are 3 types of modifications:

  • Dup Append: Insert a blank column after this column, duplicating this column (extends width by 1 pixel).
  • Dup Next: Duplicate this column to the next column (does not affect width).
  • Dup Prev: Duplicate this column to the previous column (does not affect width).

And there are 5 types of columns:

  • m Mono-cols: Perform a dup append (extends width by 1) when p2m(converting from proportional to mono).

  • dn bold-cols-DN: Perform a dup next (does not affect width) when r2b(converting from regular to bold).

  • dp bold-cols-DP: Perform a dup prev when r2b.

  • da bold-cols-DA: Perform a dup append(extends width by 1) when r2b

  • - Normal column: No modification.

Each character takes 8 bytes (5 bytes for 5 columns, 3 bytes for info including each column's type).

Example transformations:

a: This one is challenging; I need to redesign the font and try it later.

b: 4 columns, 0 da, 1 m, 2 -, 3 dp

prop(base) mono prop_b mono_b

*     *      **     **
*     *      **     **
***   ****   ****   *****
*  *  *   *  ** **  **  **
*  *  *   *  ** **  **  **
*  *  *   *  ** **  **  **
***   ****   ****   *****

c: 4 columns, 0 dn, 1 m, 2 -, 3 -

d: 4 columns, 0 dn, 1 m, 2 -, 3 da

i: 2 columns, 0 -, 1 da

j: 2 columns, 0 -, 1 da

@elulis elulis force-pushed the add-fonts branch 2 times, most recently from a0982f8 to 89775d0 Compare January 2, 2025 13:47
@elulis
Copy link
Author
elulis commented Jan 2, 2025

Hello everyone,

I've redesigned the fonts and their runtime conversion, but I've encountered a couple of issues:

  • I've noticed that there's one line not covered by unit tests (this is minor and can be easily resolved).

  • More importantly, despite spending a lot of time replacing four fonts with one, the code size has increased by an average of 300–400 bytes, which exceeds my initial expectation of 100–200 bytes.

I would appreciate any suggestions or assistance. Thank you!

@robert-hh
Copy link
Contributor

Unpacking the different fonts from a common data set required additional code, requiring space and execution time.

@elulis
Copy link
Author
elulis commented Jan 6, 2025

I realize that not every MicroPython user will need to render characters on the screen, so it's best to avoid the extra code space. The initial idea is to use a compile-time switch to decide whether to support proportional fonts. If this option is disabled during compilation, only monospace fonts will be supported while maintaining interface compatibility. This can be achieved with simple conversion code, supporting only two font types: 6-pixel wide and 8-pixel wide monospace fonts. Ideally, each character's font information would only occupy 6 bytes, saving 2 bytes per character (from 8 to 6), thus leaving some space for interface compatibility and simple conversion code.

@robert-hh
Copy link
Contributor

For me the 6x8 font and the 6x8 proportional bold fonts look best, much better than the existing 8x8 font. So my preference would be to just replace the 8x8 by the 6x8 font with an compile option to enable the other fonts as well. The 6x8 font may be smaller than the existing 8x8 font, saving a few bytes of code. And for boards with lots of flash space it would not matter to have the extra fonts & code.

@elulis
Copy link
Author
elulis commented Jan 6, 2025

Thank you for your feedback and suggestions! I'm glad to hear your thoughts on the font choices, and I appreciate your preference for the 6x8 font and the 6x8 proportional bold fonts over the existing 8x8 font.
I'd like to explain why, when disabling the compile switch to maintain the same code size, we can only use the 6x8 and 8x8 monospace fonts:

  1. Forward Compatibility: I believe that maintaining forward compatibility with the same parameters for the text method is crucial, so we still need the 8x8 monospace font.
  2. Base Font Requirements: For basic characters like 'w', which often require 5 columns for storage without involving more complex lookup tables, the most feasible method seems to be using 5 columns (5 bytes) for pixel data. This leaves only one byte for column transformation information. My current thought is that the first column is always of the Dup Next type, with the remaining four columns using 2 bits each to store one of four types: Dup Next, Dup Prev, Dup Append, and none. Although I haven't proven this method to be feasible yet, it seems intuitively possible to transition from a 6x8 monospace font to an 8x8 monospace font. Choosing a 6x8 monospace font as the base is also because transitions between monospace fonts typically don't require alignment operations.
  3. Flash Space Considerations: For most development boards, I think it's usually acceptable to enable the compile switch for all fonts, as the expected additional 300 bytes are generally manageable.
  4. Proportional & Bold Fonts: I also agree that proportional and bold fonts are very useful. Thank you for sharing this view. These fonts offer higher readability and usually take up less horizontal space on high DPI displays compared to 6x8 monospace fonts.

@robert-hh
Copy link
Contributor

Another option for providing fonts could be to proved the font directly as data from a buffer instead as font ID to the write text API. That font data could be contained in a file, which in case of the upcoming romfs file system could be directly referenced. In case of a regular file it would he to be loaded to e.g. a bytearray.

@elulis
Copy link
Author
elulis commented Jan 11, 2025

Thank you for the suggestion. This approach seems somewhat similar to font-to-py, and I feel that its complexity and the shift in the overall approach might be a bit beyond my current capabilities. I've made some progress with the method we discussed earlier about using a compile switch to simplify the font types, hoping to keep it within the same or even smaller firmware size.

@peterhinch
Copy link
Contributor

This approach seems somewhat similar to font-to-py

There is always the option of using the writer class to render a Python font file to a framebuf. Via font_to_py.py this provides a path to rendering any font...

@dpgeorge dpgeorge added the extmod Relates to extmod/ directory in source label Jan 23, 2025
@elulis
Copy link
Author
elulis commented Jan 23, 2025

After adding the compile switch MICROPY_PY_FRAMEBUF_ALL_FONTS which is disabled by default, the firmware space finally reduced to below original size. At this point, the font preview for font_id 0/1/2/3 is as follows (equivalent to only having two fonts, 0/1):

demo2

When configuring the compile switch in mpconfigport.h, the firmware space is expected to consume an additional 300 bytes. At this point, all four fonts are supported. The preview is as follows:
(edit, the following ports have enabled: ports/unix, ports/rp2, ports/esp32 )

demo

Signed-off-by: Eluli Zhang <elulis@gmail.com>
@elulis
Copy link
Author
elulis commented Jul 2, 2025

@robert-hh @peterhinch Please help me on this pull request. The firmware space issues was solved by adding a compile switch as the last comment shown. Thanks for any advices.

@peterhinch
Copy link
Contributor

I think the suggestion of @robert-hh is the way to approach this, because of its generality. Also because the font itself doesn't add to the firmware size. A suggested approach:

The user can specify any font. The font_to_py utility creates files which are precompiled when stored in ROMFS or when frozen as bytecode. The font data is stored as a linear sequence of bytes starting at _font. In the case of simple ASCII fonts these are accessed by extremely simple Python code included in the file: this could easily be replicated in C. If you want to support sparse fonts (e.g. Unicode) you also need the bytes at _sparse and slightly more involved retrieval (also included in the file and easily converted to C).

So I suggest that the user creates a file with a predefined name containing the font of choice. The C code looks for the file in ROMFS and, if found, accesses the bytes directly. In essence you are providing a minimal Writer implementation without the functionality not required in this application (handling special characters, colors etc).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
extmod Relates to extmod/ directory in source
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0