diff --git a/cyrillic_subset b/cyrillic_subset new file mode 100644 index 0000000..201fd65 --- /dev/null +++ b/cyrillic_subset @@ -0,0 +1 @@ +ЀЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРС diff --git a/font_to_py.py b/font_to_py.py index 770a3dd..c751474 100755 --- a/font_to_py.py +++ b/font_to_py.py @@ -514,6 +514,11 @@ if __name__ == "__main__": help = 'Character set. e.g. 1234567890: to restrict for a clock display.', default = '') + parser.add_argument('-k', '--charset_file', + type = str, + help = 'File containing charset e.g. cyrillic_subset.', + default = '') + args = parser.parse_args() if not args.infile[0].isalpha(): quit('Font filenames must be valid Python variable names.') @@ -552,10 +557,19 @@ if __name__ == "__main__": if args.charset and (args.smallest != 32 or args.largest != 126): print('WARNING: specified smallest and largest values ignored.') + if args.charset_file: + try: + with open(args.charset_file, 'r') as f: + cset = f.read() + except OSError: + print("Can't open", args.charset_file, 'for reading.') + sys.exit(1) + else: + cset = args.charset print('Writing Python font file.') if not write_font(args.outfile, args.infile, args.height, args.fixed, args.xmap, args.reverse, args.smallest, args.largest, - args.errchar, args.charset): + args.errchar, cset): sys.exit(1) print(args.outfile, 'written successfully.') diff --git a/writer/DRIVERS.md b/writer/DRIVERS.md index 57b3b90..6ff53ee 100644 --- a/writer/DRIVERS.md +++ b/writer/DRIVERS.md @@ -20,7 +20,8 @@ Where the buffer is held on the MicroPython host the driver should be subclassed from the official `framebuf` module. An example of such a driver is the [official SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). In addition the driver class should have bound variables `width` and `height` -containing the size of the display in pixels. +containing the size of the display in pixels. Color displays should have a +bound variable `mode` holding the `framebuf` color mode. The device driver defines a buffer of the correct size to hold a full frame of data and instantiates the `framebuf.FrameBuffer` superclass to reference it. diff --git a/writer/WRITER.md b/writer/WRITER.md index 76e2dbd..8d92d2c 100644 --- a/writer/WRITER.md +++ b/writer/WRITER.md @@ -4,6 +4,10 @@ These classes facilitate rendering Python font files to displays where the display driver is subclassed from the `framebuf` class. An example is the official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). +Basic support is for scrolling text display using multiple fonts. The +`writer_gui` module provides optional extensions for user interface objects +displayed at arbitrary locations on screen. + Example code and images are for 128*64 SSD1306 OLED displays. ![Image](images/IMG_2866.JPG) @@ -19,7 +23,7 @@ Right justified text. Mixed text and graphics. ![Image](images/fields.JPG) -Labels and Fields. +Labels and Fields (from writer_gui.py). # Contents @@ -37,9 +41,8 @@ Labels and Fields. 2.2.1 [Static Method](./WRITER.md#221-static-method) 2.2.2 [Constructor](./WRITER.md#222-constructor) 2.2.3 [Methods](./WRITER.md#223-methods) - 3. [The Label and Field classes](./WRITER.md#3-the-label-and-field-classes) + 3. [The writer_gui module](./WRITER.md#3-the-writer_gui-module) 3.1 [The Label class](./WRITER.md#31-the-label-class) - 3.2 [The Field class](./WRITER.md#32-the-field-class) 4. [Notes](./WRITER.md#4-notes) ###### [Main README](../README.md) @@ -73,12 +76,13 @@ should be used: the official SSD1306 driver is not compatible with hardware I2C ## 1.2 Files 1. `writer.py` Supports `Writer` and `CWriter` classes. - 2. `ssd1306_setup.py` Hardware initialisation for SSD1306. Requires the + 2. `writer_gui.py` Provides optional GUI objects. + 3. `ssd1306_setup.py` Hardware initialisation for SSD1306. Requires the official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). - 3. `writer_demo.py` Demo using a 128*64 SSD1306 OLED display. Import to see + 4. `writer_demo.py` Demo using a 128*64 SSD1306 OLED display. Import to see usage information. - 4. `writer_tests.py` Test/demo scripts. Import to see usage information. - 5. `writer_minimal.py` A minimal version for highly resource constrained + 5. `writer_tests.py` Test/demo scripts. Import to see usage information. + 6. `writer_minimal.py` A minimal version for highly resource constrained devices. Sample fonts: @@ -200,7 +204,8 @@ This takes the following args: ## 2.2 The CWriter class This extends the `Writer` class by adding support for upside-down and/or color -displays. +displays. A color value is an integer whose interpretation is dependent on the +display hardware and device driver. ### 2.2.1 Static method @@ -215,64 +220,76 @@ The following static method is added: ### 2.2.2 Constructor -This takes the same args as the `Writer` constructor: +This takes the following args: 1. `device` The hardware device driver instance for the screen in use. 2. `font` A Python font instance. - 3. `verbose=True` If `True` the constructor emits console printout. + 3. `fgcolor=None` Foreground color. If `None` a monochrome display is assumed. + 4. `bgcolor=None` Background color. If `None` a monochrome display is assumed. + 5. `verbose=True` If `True` the constructor emits console printout. ### 2.2.3 Methods All methods of the base class are supported. Additional method: 1. `setcolor` Args: `fgcolor=None`, `bgcolor=None`. Sets the foreground and - background colors. If either is `None` that value is left unchanged. Initial - constructor defaults are 1 and 0 for monochrome displays. Returns foreground + background colors. If one is `None` that value is left unchanged. If both + are `None` the constructor defaults are restored. Constructor defaults are + 1 and 0 for monochrome displays (`Writer`). Returns foreground and background color values. The `printstring` method works as per the base class except that the string is rendered in foreground color on background color (or reversed if `invert` is `True`). -# 3. Label and Field classes +# 3. The writer_gui module -These support applications where text is to be rendered at specific screen -locations. These classes change the text insertion point as required and are -therefore not intended for use with the writer's `printstring` method. +This supports user interface objects whose text components are drawn using the +`Writer` or `CWriter` classes. Upside down rendering is not supported: attempts +to specify it will produce unexpected results. + +The objects are drawn at specific locations on screen and are incompatible with +the display of scrolling text: they are therefore not intended for use with the +writer's `printstring` method. ## 3.1 The Label class -This renders a fixed text string to a defined screen location. +This supports applications where text is to be rendered at specific screen +locations. + +Text can be static or dynamic. In the case of dynamic text the background is +cleared to ensure that short strings can cleanly replace longer ones. + +Labels can be displayed with an optional single pixel border. + +Colors are handled flexibly. By default the colors used are those of the +`Writer` instance, however they can be changed dynamically, for example to warn +of overrange values. Constructor args: 1. `writer` The `Writer` instance (font and screen) to use. 2. `row` Location on screen. 3. `col` - 4. `text` Text to display + 4. `text` If a string is passed it is displayed: typically used for static + text. If an integer is passed it is interpreted as the maximum text length + in pixels; typically obtained from `writer.stringlen('-99.99')`. Nothing is + dsplayed until `.value()` is called. Intended for dynamic text fields. 5. `invert=False` Display in inverted or normal style. + 6. `fgcolor=None` Optionally override the `Writer` colors. + 7. `bgcolor=None` + 8. `bordercolor=False` If `False` no border is displayed. If `None` a border + is shown in the `Writer` forgeround color. If a color is passed, it is used. The constructor displays the string at the required location. -Method: - 1. `show` No args. For future use. - -## 3.2 The Field class - -Constructor args: - 1. `writer` The `Writer` instance (font and screen) to use. - 2. `row` Location on screen. - 3. `col` - 4. `max_text` Defines the longest text string the field must display. Can - either be an integer number of pixels or a text string. In the latter case the - length of the string in pixels is calculated and stored. - 5. `border=False` Optional sigle pixel border around text. - -The constructor does not cause anything to be displayed. The location of a -field is that of the top left hand corner of its text contents. If a border is -drawn, it extends in all directions beyond the text size by two pixels. - Methods: - 1. `value` Args `text`, `invert=False`. Causes the text to be displayed in - normal or inverted style. - 2. `show` No args. For future use. + 1. `value` Redraws the label. This takes the following args: + 1. `text=None` The text to display. If `None` displays last value. + 2. ` invert=False` If true, show inverse text. + 3. `fgcolor=None` Foreground color: if `None` the `Writer` default is used. + 4. `bgcolor=None` Background color, as per foreground. + 5. `bordercolor=None` As per above except that if `False` is passed, no + border is displayed. This clears a previously drawn border. + Returns the current text string. + 2. `show` No args. (Re)draws the label. For future/subclass use. # 4. Notes diff --git a/writer/writer.py b/writer/writer.py index 116f4e3..7684354 100644 --- a/writer/writer.py +++ b/writer/writer.py @@ -242,6 +242,8 @@ class Writer(): self.tab = value return self.tab + def setcolor(self, *_): + return self.fgcolor, self.bgcolor # Writer for colour displays or upside down rendering class CWriter(Writer): @@ -253,14 +255,24 @@ class CWriter(Writer): Writer.state[devid] = DisplayState() Writer.state[devid].usd = value - def __init__(self,device, font, verbose=True): + def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True): super().__init__(device, font, verbose) - - def setcolor(self, fgcolor=None, bgcolor=None): + if bgcolor is not None: # Assume monochrome. + self.bgcolor = bgcolor if fgcolor is not None: self.fgcolor = fgcolor - if bgcolor is not None: - self.bgcolor = bgcolor + self.def_bgcolor = self.bgcolor + self.def_fgcolor = self.fgcolor + + def setcolor(self, fgcolor=None, bgcolor=None): + if fgcolor is None and bgcolor is None: + self.fgcolor = self.def_fgcolor + self.bgcolor = self.def_bgcolor + else: + if fgcolor is not None: + self.fgcolor = fgcolor + if bgcolor is not None: + self.bgcolor = bgcolor return self.fgcolor, self.bgcolor def _printchar(self, char, invert=False, recurse=False): @@ -296,48 +308,3 @@ class CWriter(Writer): break s.text_col += -char_width if usd else char_width self.cpos += 1 - -class Label(): - def __init__(self, writer, row, col, text, invert=False): - self.writer = writer - self.row = row - self.col = col - self.invert = invert - if isinstance(text, int): - self.length = text - self.text = '' - else: - self.length = writer.stringlen(text) - self.text = text - if not isinstance(self, Field): - self.show() - - def show(self): - wri = self.writer - dev = wri.device - Writer.set_textpos(dev, self.row, self.col) - wri.printstring(self.text, self.invert) - -class Field(Label): - def __init__(self, writer, row, col, max_text, border=False): - super().__init__(writer, row, col, max_text, False) - self.border = border - - def value(self, text, invert=False): - self.text = text - self.invert = invert - self.show() - - def show(self): - wri = self.writer - dev = wri.device - if wri._getstate().usd: - row = dev.height - self.row - wri.height() - col = dev.width - self.col - self.length - else: - row = self.row - col = self.col - dev.fill_rect(col, row, self.length, wri.height(), wri.bgcolor) - if self.border: - dev.rect(col - 2, row - 2, self.length + 4, wri.height() + 4, wri.fgcolor) - super().show() diff --git a/writer/writer_tests.py b/writer/writer_tests.py index 0144c68..1979461 100644 --- a/writer/writer_tests.py +++ b/writer/writer_tests.py @@ -32,7 +32,8 @@ import machine import utime import uos from ssd1306_setup import WIDTH, HEIGHT, setup -from writer import Writer, CWriter, Field, Label +from writer import Writer, CWriter +from writer_gui import Label # Fonts import freesans20 @@ -179,29 +180,9 @@ def fields(use_spi=False, soft=True): Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it wri = Writer(ssd, fixed, verbose=False) wri.set_clip(False, False, False) - textfield = Field(wri, 0, 2, 'longer', False) - numfield = Field(wri, 25, 2, '99.99', True) - countfield = Field(wri, 0, 90, '1', False) - n = 1 - for s in ('short', 'longer', '1', ''): - textfield.value(s) - numfield.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000)) - countfield.value('{:1d}'.format(n)) - n += 1 - ssd.show() - utime.sleep(2) - textfield.value('Done', True) - ssd.show() - -def usd_fields(use_spi=False, soft=True): - ssd = setup(use_spi, soft) # Create a display instance - CWriter.invert_display(ssd) - CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it - wri = CWriter(ssd, fixed, verbose=False) - wri.set_clip(False, False, False) - textfield = Field(wri, 25, 2, 'longer', False) - numfield = Field(wri, 2, 2, '99.99', True) - countfield = Field(wri, 25, 100, '1', False) + textfield = Label(wri, 0, 2, wri.stringlen('longer')) + numfield = Label(wri, 25, 2, wri.stringlen('99.99'), bordercolor=None) + countfield = Label(wri, 0, 90, wri.stringlen('1')) n = 1 for s in ('short', 'longer', '1', ''): textfield.value(s) @@ -223,14 +204,16 @@ def multi_fields(use_spi=False, soft=True): dy = small.height() + 6 y = 2 col = 15 + width = wri.stringlen('99.99') for txt in ('X:', 'Y:', 'Z:'): Label(wri, y, 0, txt) - nfields.append(Field(wri, y, col, '99.99', True)) + nfields.append(Label(wri, y, col, width, bordercolor=None)) # Draw border y += dy for _ in range(10): for field in nfields: - field.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000)) + value = int.from_bytes(uos.urandom(3),'little')/167722 + field.value('{:5.2f}'.format(value)) ssd.show() utime.sleep(1) Label(wri, 0, 64, ' DONE ', True) @@ -254,13 +237,14 @@ def dual(use_spi=False, soft=True): y = 2 for txt in ('X:', 'Y:', 'Z:'): Label(wri, y, 0, txt) - nfields[n].append(Field(wri, y, col, '99.99', True)) + nfields[n].append(Label(wri, y, col, wri.stringlen('99.99'), True)) y += dy for _ in range(10): for n, wri in enumerate((wri0, wri1)): for field in nfields[n]: - field.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000)) + value = int.from_bytes(uos.urandom(3),'little')/167722 + field.value('{:5.2f}'.format(value)) wri.device.show() utime.sleep(1) for wri in (wri0, wri1): @@ -284,9 +268,8 @@ fonts() Two fonts. tabs() Tab stops. usd_tabs() Upside-down tabs. wrap() Word wrapping -fields() Field test with dynamic data. -usd_fields() Upside down fields. -multi_fields() Fields and labels. +fields() Label test with dynamic data. +multi_fields() More Labels. dual() Test two displays on one host.''' print(tstr)