diff --git a/src/bar_code.toit b/src/bar_code.toit index 00df960..13c3eea 100644 --- a/src/bar_code.toit +++ b/src/bar_code.toit @@ -2,6 +2,12 @@ // Use of this source code is governed by an MIT-style license that can be // found in the LICENSE file. +import font show Font + +import .common +import .element show CustomElement +import .style + EAN_13_QUIET_ZONE_WIDTH ::= 9 EAN_13_START_WIDTH ::= 3 EAN_13_MIDDLE_WIDTH ::= 5 @@ -15,3 +21,110 @@ EAN_13_L_CODES_ ::= [0x0d, 0x19, 0x13, 0x3d, 0x23, 0x31, 0x2f, 0x3b, 0x37, 0x0b] EAN_13_G_CODES_ ::= [0x27, 0x33, 0x1b, 0x21, 0x1d, 0x39, 0x05, 0x11, 0x09, 0x17] // Encoding of the first (invisible) digit. EAN_13_FIRST_CODES_ ::= [0x00, 0x0b, 0x0d, 0x0e, 0x13, 0x19, 0x1c, 0x15, 0x16, 0x1a] + +// Element that draws a standard EAN-13 bar code. TODO: Other scales. +class BarCodeEanElement extends CustomElement: + color_/int? := 0 + background_ := 0xff + sans10_ ::= Font.get "sans10" + number_height_ := EAN_13_BOTTOM_SPACE + + type -> string: return "bar-code-ean" + + set_attribute key/string value -> none: + if key == "color": + if color_ != value: + invalidate + color_ = value + else: + super key value + + code_/string := ? // 13 digit code as a string. + + code= value/string -> none: + if value != code_: invalidate + code_ = value + + code -> string: return code_ + + /** + $code_: The 13 digit product code. + $x: The left edge of the barcode in the coordinate system of the transform. + $y: The top edge of the barcode in the coordinate system of the transform. + Use $set_styles to set the background to white and the color to black. + */ + constructor .code_/string --x/int?=null --y/int?=null: + // The numbers go below the bar code in a way that depends on the size + // of the digits, so we need to take that into account when calculating + // the bounding box. + number_height_ = (sans10_.text_extent "8")[1] + height := EAN_13_HEIGHT + number_height_ - EAN_13_BOTTOM_SPACE + w := EAN_13_WIDTH + h := height + 1 + super --x=x --y=y --w=w --h=h + + l_ digit: + return EAN_13_L_CODES_[digit & 0xf] + + g_ digit: + return EAN_13_G_CODES_[digit & 0xf] + + r_ digit: + return (l_ digit) ^ 0x7f + + // Make a white background behind the bar code and draw the digits along the bottom. + draw_background_ canvas/Canvas: + // Bar code coordinates. + text_x := EAN_13_QUIET_ZONE_WIDTH + EAN_13_START_WIDTH + text_y := EAN_13_HEIGHT + number_height_ - EAN_13_BOTTOM_SPACE + 1 + + canvas.text 1 text_y --text=code_[..1] --color=color_ --font=sans10_ + + code_[1..7].split "": + if it != "": + canvas.text text_x text_y --text=it --color=color_ --font=sans10_ + text_x += EAN_13_DIGIT_WIDTH + text_x += EAN_13_MIDDLE_WIDTH - 1 + code_[7..13].split "": + if it != "": + canvas.text text_x text_y --text=it --color=color_ --font=sans10_ + text_x += EAN_13_DIGIT_WIDTH + marker_width := (sans10_.text_extent ">")[0] + text_x += EAN_13_START_WIDTH + EAN_13_QUIET_ZONE_WIDTH - marker_width + canvas.text text_x text_y --text=">" --color=color_ --font=sans10_ + + // Redraw routine. + custom_draw canvas/Canvas: + draw_background_ canvas + + x := EAN_13_QUIET_ZONE_WIDTH + long_height := EAN_13_HEIGHT + short_height := EAN_13_HEIGHT - EAN_13_BOTTOM_SPACE + // Start bars: 101. + canvas.rectangle x 0 --w=1 --h=long_height --color=color_ + canvas.rectangle x + 2 0 --w=1 --h=long_height --color=color_ + x += 3 + first_code := EAN_13_FIRST_CODES_[code_[0] & 0xf] + // Left digits using the L or G mapping. + for i := 1; i < 7; i++: + digit := code_[i] + code := ((first_code >> (6 - i)) & 1) == 0 ? (l_ digit) : (g_ digit) + for b := 6; b >= 0; b--: + if ((1 << b) & code) != 0: + canvas.rectangle x 0 --w=1 --h=short_height --color=color_ + x++ + // Middle bars: 01010 + canvas.rectangle x + 1 0 --w=1 --h=long_height --color=color_ + canvas.rectangle x + 3 0 --w=1 --h=long_height --color=color_ + x += 5 + // Left digits using the R mapping. + for i := 7; i < 13; i++: + digit := code_[i] + code := r_ digit + for b := 6; b >= 0; b--: + if ((1 << b) & code) != 0: + canvas.rectangle x 0 --w=1 --h=short_height --color=color_ + x++ + // End bars: 101. + canvas.rectangle x 0 --w=1 --h=long_height --color=color_ + canvas.rectangle x + 2 0 --w=1 --h=long_height --color=color_ diff --git a/tests/barcode_visualized.toit b/tests/barcode_visualized.toit new file mode 100644 index 0000000..b79d40b --- /dev/null +++ b/tests/barcode_visualized.toit @@ -0,0 +1,37 @@ +// Copyright (C) 2023 Toitware ApS. +// Use of this source code is governed by a Zero-Clause BSD license that can +// be found in the TESTS_LICENSE file. + +// Tests drawing a supermarket-style bar code on a pixel display. + +import expect show * +import pixel_display show * +import pixel_display.bar_code show * +import pixel_display.element show * +import pixel_display.style show Style +import .png_visualizer + +main args: + if args.size != 1: + print "Usage: script.toit png-basename" + exit 1 + driver := SeveralColorPngVisualizer 120 160 args[0] --outline=SEVERAL_BLUE + display := SeveralColorPixelDisplay driver --portrait=false + display.background = SEVERAL_GRAY + + style := Style --type_map={ + "bar-code-ean": Style --color=SEVERAL_BLACK --background=SEVERAL_WHITE, + } + + barcode := BarCodeEanElement --x=15 --y=15 "4035999001512" + display.add barcode + display.set_styles [style] + display.draw + + barcode.move_to 20 20 + display.draw + + barcode.code = "4000417020000" + display.draw + + driver.write_png diff --git a/tests/gold/barcode_visualized.toit.png b/tests/gold/barcode_visualized.toit.png new file mode 100644 index 0000000..3a9eb71 Binary files /dev/null and b/tests/gold/barcode_visualized.toit.png differ