mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
dot matrix improvements
This commit is contained in:
@@ -343,7 +343,7 @@ public class MakerBoardPainter {
|
||||
* nothing. The variant string reads "size, interface", and breaking it at the comma gives one
|
||||
* property per line, which keeps the longest line well inside the glass instead of spanning it.
|
||||
*/
|
||||
private static String screenText(Display display, String name, String value) {
|
||||
public static String screenText(Display display, String name, String value) {
|
||||
boolean hasValue = value != null && !value.trim().isEmpty();
|
||||
String variant = hasValue ? value.replace(", ", "\n") : null;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.util.List;
|
||||
|
||||
import org.diylc.awt.StringUtils;
|
||||
import org.diylc.common.Display;
|
||||
@@ -304,11 +305,7 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
g2d.setColor(screenColor);
|
||||
g2d.fill(new Rectangle2D.Double(screenX, screenY, screenW, screenH));
|
||||
|
||||
Rectangle2D screenArea = new Rectangle2D.Double(screenX, screenY, screenW, screenH);
|
||||
drawCharacterMatrix(g2d, screenArea);
|
||||
|
||||
MakerBoardPainter.drawScreenText(g2d, screenArea, getScreenInk(), getScreen(), getName(),
|
||||
getValueForDisplay());
|
||||
drawCharacterMatrix(g2d, new Rectangle2D.Double(screenX, screenY, screenW, screenH));
|
||||
|
||||
// Only the parallel row has anywhere to print. The backpack's column sits 2.5 mm from the
|
||||
// left edge with the bezel 1.4 mm further in, so a label beside it would lie across the metal
|
||||
@@ -327,7 +324,8 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
}
|
||||
|
||||
/**
|
||||
* The unlit dot grid, centred in the lit area. The matrix is narrower than the glass on both
|
||||
* The character grid, centred in the lit area: every cell's dots in the unlit shade first, then
|
||||
* the dots the text lights over the top of them. The matrix is narrower than the glass on both
|
||||
* modules, which is why it is centred rather than inset by a margin.
|
||||
*/
|
||||
private void drawCharacterMatrix(Graphics2D g2d, Rectangle2D screenArea) {
|
||||
@@ -343,7 +341,8 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
double matrixX = screenArea.getX() + (screenArea.getWidth() - matrixWidth) / 2.0;
|
||||
double matrixY = screenArea.getY() + (screenArea.getHeight() - matrixHeight) / 2.0;
|
||||
|
||||
// The 20x4 comes to 3200 dots, so the rectangle is reused rather than allocated per dot.
|
||||
// The 20x4 comes to 3200 dots, so the rectangle is reused rather than allocated per dot, and
|
||||
// the two shades are laid down in one pass each rather than a colour change per dot.
|
||||
Rectangle2D.Double dot = new Rectangle2D.Double(0, 0, dotSize, dotSize);
|
||||
|
||||
g2d.setColor(getDotColor());
|
||||
@@ -360,6 +359,33 @@ public class CharacterLCD extends AbstractMakerBoard {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<String> lines = DotMatrixScreen.layOutText(getScreen(), getName(),
|
||||
getValueForDisplay(), lcdSize.getColumns(), lcdSize.getRows());
|
||||
if (lines.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
g2d.setColor(getScreenInk());
|
||||
int firstRow = (lcdSize.getRows() - lines.size()) / 2;
|
||||
for (int line = 0; line < lines.size(); line++) {
|
||||
String text = lines.get(line);
|
||||
int firstColumn = (lcdSize.getColumns() - text.length()) / 2;
|
||||
double cellY = matrixY + (firstRow + line) * charPitchY;
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
double cellX = matrixX + (firstColumn + i) * charPitchX;
|
||||
for (int dotRow = 0; dotRow < DotMatrixFont.GLYPH_HEIGHT; dotRow++) {
|
||||
for (int dotColumn = 0; dotColumn < DotMatrixFont.GLYPH_WIDTH; dotColumn++) {
|
||||
if (DotMatrixFont.isDotLit(c, dotRow, dotColumn)) {
|
||||
dot.x = cellX + dotColumn * dotPitch;
|
||||
dot.y = cellY + dotRow * dotPitch;
|
||||
g2d.fill(dot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
/**
|
||||
* The glyphs an HD44780 character LCD paints in its 5x8 cell, one entry per printable ASCII
|
||||
* character. A glyph is stored as five columns, one per dot column, with bit 0 the top row -- the
|
||||
* order the controller itself clocks them out.
|
||||
*
|
||||
* <p>Most glyphs sit on a baseline in the seventh row and leave the eighth empty, which is where
|
||||
* the cursor would sit. The ones that descend below the baseline on a real module -- the lower-case
|
||||
* g, j, p, q and y, the comma, the semicolon and the underscore -- do use that eighth row, so they
|
||||
* are drawn to the full eight.
|
||||
*
|
||||
* <p>The table is spelled out rather than sampled from a system font because a rendered project has
|
||||
* to come out identical on every machine, and a font that may or may not be installed cannot
|
||||
* promise that.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class DotMatrixFont {
|
||||
|
||||
public static final int GLYPH_WIDTH = 5;
|
||||
public static final int GLYPH_HEIGHT = 8;
|
||||
|
||||
private static final char FIRST_CHAR = ' ';
|
||||
private static final char LAST_CHAR = '~';
|
||||
// what stands in for anything the table has no glyph for, as the controller's ROM does
|
||||
private static final char FALLBACK_CHAR = '?';
|
||||
|
||||
private static final int[] GLYPH_COLUMNS = new int[] {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // space
|
||||
0x00, 0x00, 0x5F, 0x00, 0x00, // !
|
||||
0x00, 0x07, 0x00, 0x07, 0x00, // "
|
||||
0x14, 0x7F, 0x14, 0x7F, 0x14, // #
|
||||
0x24, 0x2A, 0x7F, 0x2A, 0x12, // $
|
||||
0x23, 0x13, 0x08, 0x64, 0x62, // %
|
||||
0x36, 0x49, 0x55, 0x22, 0x50, // &
|
||||
0x00, 0x05, 0x03, 0x00, 0x00, // '
|
||||
0x00, 0x1C, 0x22, 0x41, 0x00, // (
|
||||
0x00, 0x41, 0x22, 0x1C, 0x00, // )
|
||||
0x14, 0x08, 0x3E, 0x08, 0x14, // *
|
||||
0x08, 0x08, 0x3E, 0x08, 0x08, // +
|
||||
0x00, 0x80, 0xC0, 0x40, 0x00, // ,
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, // -
|
||||
0x00, 0x60, 0x60, 0x00, 0x00, // .
|
||||
0x20, 0x10, 0x08, 0x04, 0x02, // /
|
||||
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
|
||||
0x00, 0x42, 0x7F, 0x40, 0x00, // 1
|
||||
0x42, 0x61, 0x51, 0x49, 0x46, // 2
|
||||
0x21, 0x41, 0x45, 0x4B, 0x31, // 3
|
||||
0x18, 0x14, 0x12, 0x7F, 0x10, // 4
|
||||
0x27, 0x45, 0x45, 0x45, 0x39, // 5
|
||||
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
|
||||
0x01, 0x71, 0x09, 0x05, 0x03, // 7
|
||||
0x36, 0x49, 0x49, 0x49, 0x36, // 8
|
||||
0x06, 0x49, 0x49, 0x29, 0x1E, // 9
|
||||
0x00, 0x36, 0x36, 0x00, 0x00, // :
|
||||
0x80, 0x76, 0x36, 0x00, 0x00, // ;
|
||||
0x00, 0x08, 0x14, 0x22, 0x41, // <
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, // =
|
||||
0x41, 0x22, 0x14, 0x08, 0x00, // >
|
||||
0x02, 0x01, 0x51, 0x09, 0x06, // ?
|
||||
0x32, 0x49, 0x79, 0x41, 0x3E, // @
|
||||
0x7E, 0x11, 0x11, 0x11, 0x7E, // A
|
||||
0x7F, 0x49, 0x49, 0x49, 0x36, // B
|
||||
0x3E, 0x41, 0x41, 0x41, 0x22, // C
|
||||
0x7F, 0x41, 0x41, 0x22, 0x1C, // D
|
||||
0x7F, 0x49, 0x49, 0x49, 0x41, // E
|
||||
0x7F, 0x09, 0x09, 0x09, 0x01, // F
|
||||
0x3E, 0x41, 0x49, 0x49, 0x7A, // G
|
||||
0x7F, 0x08, 0x08, 0x08, 0x7F, // H
|
||||
0x00, 0x41, 0x7F, 0x41, 0x00, // I
|
||||
0x20, 0x40, 0x41, 0x3F, 0x01, // J
|
||||
0x7F, 0x08, 0x14, 0x22, 0x41, // K
|
||||
0x7F, 0x40, 0x40, 0x40, 0x40, // L
|
||||
0x7F, 0x02, 0x0C, 0x02, 0x7F, // M
|
||||
0x7F, 0x04, 0x08, 0x10, 0x7F, // N
|
||||
0x3E, 0x41, 0x41, 0x41, 0x3E, // O
|
||||
0x7F, 0x09, 0x09, 0x09, 0x06, // P
|
||||
0x3E, 0x41, 0x51, 0x21, 0x5E, // Q
|
||||
0x7F, 0x09, 0x19, 0x29, 0x46, // R
|
||||
0x46, 0x49, 0x49, 0x49, 0x31, // S
|
||||
0x01, 0x01, 0x7F, 0x01, 0x01, // T
|
||||
0x3F, 0x40, 0x40, 0x40, 0x3F, // U
|
||||
0x1F, 0x20, 0x40, 0x20, 0x1F, // V
|
||||
0x3F, 0x40, 0x38, 0x40, 0x3F, // W
|
||||
0x63, 0x14, 0x08, 0x14, 0x63, // X
|
||||
0x07, 0x08, 0x70, 0x08, 0x07, // Y
|
||||
0x61, 0x51, 0x49, 0x45, 0x43, // Z
|
||||
0x00, 0x7F, 0x41, 0x41, 0x00, // [
|
||||
0x02, 0x04, 0x08, 0x10, 0x20, // \
|
||||
0x00, 0x41, 0x41, 0x7F, 0x00, // ]
|
||||
0x04, 0x02, 0x01, 0x02, 0x04, // ^
|
||||
0x80, 0x80, 0x80, 0x80, 0x80, // _
|
||||
0x00, 0x01, 0x02, 0x04, 0x00, // `
|
||||
0x20, 0x54, 0x54, 0x54, 0x78, // a
|
||||
0x7F, 0x48, 0x44, 0x44, 0x38, // b
|
||||
0x38, 0x44, 0x44, 0x44, 0x20, // c
|
||||
0x38, 0x44, 0x44, 0x48, 0x7F, // d
|
||||
0x38, 0x54, 0x54, 0x54, 0x18, // e
|
||||
0x08, 0x7E, 0x09, 0x01, 0x02, // f
|
||||
0x18, 0xA4, 0xA4, 0xA4, 0x7C, // g
|
||||
0x7F, 0x08, 0x04, 0x04, 0x78, // h
|
||||
0x00, 0x44, 0x7D, 0x40, 0x00, // i
|
||||
0x40, 0x80, 0x80, 0x7D, 0x00, // j
|
||||
0x7F, 0x10, 0x28, 0x44, 0x00, // k
|
||||
0x00, 0x41, 0x7F, 0x40, 0x00, // l
|
||||
0x7C, 0x04, 0x18, 0x04, 0x78, // m
|
||||
0x7C, 0x08, 0x04, 0x04, 0x78, // n
|
||||
0x38, 0x44, 0x44, 0x44, 0x38, // o
|
||||
0xFC, 0x24, 0x24, 0x24, 0x18, // p
|
||||
0x18, 0x24, 0x24, 0x24, 0xFC, // q
|
||||
0x7C, 0x08, 0x04, 0x04, 0x08, // r
|
||||
0x48, 0x54, 0x54, 0x54, 0x20, // s
|
||||
0x04, 0x3F, 0x44, 0x40, 0x20, // t
|
||||
0x3C, 0x40, 0x40, 0x20, 0x7C, // u
|
||||
0x1C, 0x20, 0x40, 0x20, 0x1C, // v
|
||||
0x3C, 0x40, 0x30, 0x40, 0x3C, // w
|
||||
0x44, 0x28, 0x10, 0x28, 0x44, // x
|
||||
0x1C, 0xA0, 0xA0, 0xA0, 0x7C, // y
|
||||
0x44, 0x64, 0x54, 0x4C, 0x44, // z
|
||||
0x00, 0x08, 0x36, 0x41, 0x00, // {
|
||||
0x00, 0x00, 0x7F, 0x00, 0x00, // |
|
||||
0x00, 0x41, 0x36, 0x08, 0x00, // }
|
||||
0x08, 0x08, 0x2A, 0x1C, 0x08, // ~
|
||||
};
|
||||
|
||||
private DotMatrixFont() {}
|
||||
|
||||
/** Whether the glyph for a character lights the dot at the given row and column of its cell. */
|
||||
public static boolean isDotLit(char c, int row, int column) {
|
||||
if (row < 0 || row >= GLYPH_HEIGHT || column < 0 || column >= GLYPH_WIDTH) {
|
||||
return false;
|
||||
}
|
||||
char glyph = c < FIRST_CHAR || c > LAST_CHAR ? FALLBACK_CHAR : c;
|
||||
return (GLYPH_COLUMNS[(glyph - FIRST_CHAR) * GLYPH_WIDTH + column] >> row & 1) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.diylc.common.Display;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
|
||||
/**
|
||||
* Text as a dot-matrix screen actually shows it: folded into the character grid the panel has room
|
||||
* for, then painted one lit pixel at a time out of {@link DotMatrixFont}.
|
||||
*
|
||||
* <p>Laying the text out is shared by every such screen; painting it is not. A graphic panel like
|
||||
* the Nokia's addresses one unbroken grid of pixels, and {@link #drawText} serves those. A
|
||||
* character module instead has gaps between its cells, so it walks its own cells and only borrows
|
||||
* the layout.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class DotMatrixScreen {
|
||||
|
||||
/** A glyph and the blank column that separates it from the next, as the panel fonts are drawn. */
|
||||
public static final int CELL_WIDTH = DotMatrixFont.GLYPH_WIDTH + 1;
|
||||
/** Rows butt against each other; the glyph's own last row is the gap. */
|
||||
public static final int CELL_HEIGHT = DotMatrixFont.GLYPH_HEIGHT;
|
||||
|
||||
private DotMatrixScreen() {}
|
||||
|
||||
/**
|
||||
* What the screen spells out, broken into grid rows. A line too wide for the grid is folded at a
|
||||
* space where there is one and cut where there is not, and whatever runs past the last row is
|
||||
* dropped: a real panel shows what fits and nothing more.
|
||||
*/
|
||||
public static List<String> layOutText(Display display, String name, String value, int columns,
|
||||
int rows) {
|
||||
String text = MakerBoardPainter.screenText(display, name, value);
|
||||
if (text == null || columns <= 0 || rows <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> lines = new ArrayList<String>();
|
||||
for (String paragraph : text.split("\n")) {
|
||||
String remaining = paragraph.trim();
|
||||
while (!remaining.isEmpty() && lines.size() < rows) {
|
||||
if (remaining.length() <= columns) {
|
||||
lines.add(remaining);
|
||||
break;
|
||||
}
|
||||
int fold = remaining.lastIndexOf(' ', columns);
|
||||
if (fold <= 0) {
|
||||
fold = columns;
|
||||
}
|
||||
lines.add(remaining.substring(0, fold).trim());
|
||||
remaining = remaining.substring(fold).trim();
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints laid-out text onto an unbroken pixel grid, centred in it. The caller has already set the
|
||||
* ink colour and placed the grid; this only decides which pixels light.
|
||||
*
|
||||
* <p>Centring is worked out in pixels rather than whole cells. A panel addresses single pixels,
|
||||
* and a line that nearly fills the grid has no whole cell to spare, so rounding to one would pin
|
||||
* it against the left edge and leave the slack -- the trailing cell's blank column, plus whatever
|
||||
* the cell width does not divide into the panel -- piled up on the right.
|
||||
*
|
||||
* @param lines text from {@link #layOutText}
|
||||
* @param pixelsX pixels the panel is wide
|
||||
* @param pixelsY pixels the panel is tall
|
||||
* @param x left edge of the pixel grid
|
||||
* @param y top edge of the pixel grid
|
||||
* @param pixelWidth width of one display pixel
|
||||
* @param pixelHeight height of one display pixel, which need not match its width
|
||||
*/
|
||||
public static void drawText(Graphics2D g2d, List<String> lines, int pixelsX, int pixelsY,
|
||||
double x, double y, double pixelWidth, double pixelHeight) {
|
||||
if (lines.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// a panel runs to thousands of pixels, so the rectangle is reused rather than allocated per dot
|
||||
Rectangle2D.Double pixel = new Rectangle2D.Double(0, 0, pixelWidth, pixelHeight);
|
||||
|
||||
// the offsets land on whole pixels because that is all the controller can address
|
||||
double firstRow = Math.floor((pixelsY - lines.size() * CELL_HEIGHT) / 2.0);
|
||||
for (int line = 0; line < lines.size(); line++) {
|
||||
String text = lines.get(line);
|
||||
// the spacer column trailing the last glyph is not part of the text, so it is not centred
|
||||
// with it
|
||||
int lineWidth = text.length() * CELL_WIDTH - (CELL_WIDTH - DotMatrixFont.GLYPH_WIDTH);
|
||||
double firstColumn = Math.floor((pixelsX - lineWidth) / 2.0);
|
||||
double cellY = y + (firstRow + line * CELL_HEIGHT) * pixelHeight;
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
double cellX = x + (firstColumn + i * CELL_WIDTH) * pixelWidth;
|
||||
for (int dotRow = 0; dotRow < DotMatrixFont.GLYPH_HEIGHT; dotRow++) {
|
||||
for (int dotColumn = 0; dotColumn < DotMatrixFont.GLYPH_WIDTH; dotColumn++) {
|
||||
if (DotMatrixFont.isDotLit(c, dotRow, dotColumn)) {
|
||||
pixel.x = cellX + dotColumn * pixelWidth;
|
||||
pixel.y = cellY + dotRow * pixelHeight;
|
||||
g2d.fill(pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,11 @@ public class Nokia5110LCD extends AbstractMakerBoard {
|
||||
public static Size CORNER_RADIUS = new Size(1.5d, SizeUnit.mm);
|
||||
public static Size SILK_TEXT_GAP = new Size(1.0d, SizeUnit.mm);
|
||||
|
||||
// The PCD8544's own addressable grid. Its pixels are not square, the glass being wider than it
|
||||
// is tall by more than 84:48, so the two pitches are worked out separately.
|
||||
public static final int PIXELS_X = 84;
|
||||
public static final int PIXELS_Y = 48;
|
||||
|
||||
public static final String[] PIN_NAMES =
|
||||
new String[] {"RST", "CE", "DC", "DIN", "CLK", "VCC", "BL", "GND"};
|
||||
|
||||
@@ -253,9 +258,14 @@ public class Nokia5110LCD extends AbstractMakerBoard {
|
||||
g2d.draw(new RoundRectangle2D.Double(displayX, displayY, displayW, displayH, displayRadius,
|
||||
displayRadius));
|
||||
|
||||
MakerBoardPainter.drawScreenText(g2d,
|
||||
new Rectangle2D.Double(displayX, displayY, displayW, displayH), getScreenInk(),
|
||||
getScreen(), getName(), getValueForDisplay());
|
||||
// The panel has no other way to show text, so the label is drawn the way the module would
|
||||
// put it there: lit pixels of its own grid rather than a smooth font laid over the glass.
|
||||
int columns = PIXELS_X / DotMatrixScreen.CELL_WIDTH;
|
||||
int rows = PIXELS_Y / DotMatrixScreen.CELL_HEIGHT;
|
||||
g2d.setColor(getScreenInk());
|
||||
DotMatrixScreen.drawText(g2d,
|
||||
DotMatrixScreen.layOutText(getScreen(), getName(), getValueForDisplay(), columns, rows),
|
||||
PIXELS_X, PIXELS_Y, displayX, displayY, displayW / PIXELS_X, displayH / PIXELS_Y);
|
||||
|
||||
// Names lie flat along the row: the strip is 3.5 mm deep and a label stood on end needs
|
||||
// 4.7 mm.
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
import org.diylc.awt.StringUtils;
|
||||
@@ -346,9 +345,16 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
g2d.setColor(ACTIVE_AREA_COLOR);
|
||||
g2d.fill(new RoundRectangle2D.Double(activeX, activeY, activeW, activeH, 2, 2));
|
||||
|
||||
MakerBoardPainter.drawScreenText(g2d,
|
||||
new Rectangle2D.Double(activeX, activeY, activeW, activeH), getPixelColor(), getScreen(),
|
||||
getName(), getValueForDisplay());
|
||||
// Drawn as lit pixels of the controller's own grid, which is the only way this panel can
|
||||
// put text on the glass.
|
||||
int pixelsX = version.getPixelsX();
|
||||
int pixelsY = version.getPixelsY();
|
||||
int columns = pixelsX / DotMatrixScreen.CELL_WIDTH;
|
||||
int rows = pixelsY / DotMatrixScreen.CELL_HEIGHT;
|
||||
g2d.setColor(getPixelColor());
|
||||
DotMatrixScreen.drawText(g2d,
|
||||
DotMatrixScreen.layOutText(getScreen(), getName(), getValueForDisplay(), columns, rows),
|
||||
pixelsX, pixelsY, activeX, activeY, activeW / pixelsX, activeH / pixelsY);
|
||||
|
||||
drawPinNames(g2d, boardX, boardY);
|
||||
}
|
||||
@@ -405,15 +411,17 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
// Panel and lit-area sizes belong to the display part and are shared by both of a size's
|
||||
// boards. Everything belonging to a PCB is on the Layout, including where the lit area sits
|
||||
// down the board, which the two 0.91" boards disagree on.
|
||||
SSD1306_0_91("0.91\" SSD1306 128x32", 30.0d, 12.0d, 22.384d, 5.584d, 2.1d,
|
||||
SSD1306_0_91("0.91\" SSD1306 128x32", 128, 32, 30.0d, 12.0d, 22.384d, 5.584d, 2.1d,
|
||||
new Layout(35.8d, 12.0d, 1.5d, true, 0d, 0d, 5.0d, 0d, 2.35d),
|
||||
new Layout(32.5d, 20.5d, 1.5d, false, 2.5d, 2.0d, 1.25d, 4.0d, 6.6d)),
|
||||
SSD1306_0_96("0.96\" SSD1306 128x64", 26.7d, 19.26d, 21.74d, 11.0d, 2.48d,
|
||||
SSD1306_0_96("0.96\" SSD1306 128x64", 128, 64, 26.7d, 19.26d, 21.74d, 11.0d, 2.48d,
|
||||
new Layout(27.0d, 27.0d, 1.6d, false, 2.0d, 2.0d, 0.15d, 3.87d, 6.14d)),
|
||||
SH1106_1_3("1.3\" SH1106 128x64", 35.4d, 23.0d, 29.42d, 14.7d, 2.99d,
|
||||
SH1106_1_3("1.3\" SH1106 128x64", 128, 64, 35.4d, 23.0d, 29.42d, 14.7d, 2.99d,
|
||||
new Layout(35.4d, 33.5d, 1.5d, false, 2.5d, 3.0d, 0d, 5.25d, 7.35d));
|
||||
|
||||
private final String label;
|
||||
private final int pixelsX;
|
||||
private final int pixelsY;
|
||||
private final double panelWidthMm;
|
||||
private final double panelLengthMm;
|
||||
private final double activeWidthMm;
|
||||
@@ -423,15 +431,18 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
private final Layout spiLayout;
|
||||
|
||||
/** For a size sold on one board, which both headers are built onto. */
|
||||
Version(String label, double panelWidthMm, double panelLengthMm, double activeWidthMm,
|
||||
double activeLengthMm, double activeLeftInPanelMm, Layout layout) {
|
||||
this(label, panelWidthMm, panelLengthMm, activeWidthMm, activeLengthMm, activeLeftInPanelMm,
|
||||
layout, layout);
|
||||
Version(String label, int pixelsX, int pixelsY, double panelWidthMm, double panelLengthMm,
|
||||
double activeWidthMm, double activeLengthMm, double activeLeftInPanelMm, Layout layout) {
|
||||
this(label, pixelsX, pixelsY, panelWidthMm, panelLengthMm, activeWidthMm, activeLengthMm,
|
||||
activeLeftInPanelMm, layout, layout);
|
||||
}
|
||||
|
||||
Version(String label, double panelWidthMm, double panelLengthMm, double activeWidthMm,
|
||||
double activeLengthMm, double activeLeftInPanelMm, Layout i2cLayout, Layout spiLayout) {
|
||||
Version(String label, int pixelsX, int pixelsY, double panelWidthMm, double panelLengthMm,
|
||||
double activeWidthMm, double activeLengthMm, double activeLeftInPanelMm, Layout i2cLayout,
|
||||
Layout spiLayout) {
|
||||
this.label = label;
|
||||
this.pixelsX = pixelsX;
|
||||
this.pixelsY = pixelsY;
|
||||
this.panelWidthMm = panelWidthMm;
|
||||
this.panelLengthMm = panelLengthMm;
|
||||
this.activeWidthMm = activeWidthMm;
|
||||
@@ -444,6 +455,8 @@ public class OLEDDisplay extends AbstractMakerBoard {
|
||||
@Override public String toString() { return label; }
|
||||
public double getPanelWidthMm() { return panelWidthMm; }
|
||||
public double getPanelLengthMm() { return panelLengthMm; }
|
||||
public int getPixelsX() { return pixelsX; }
|
||||
public int getPixelsY() { return pixelsY; }
|
||||
public double getActiveWidthMm() { return activeWidthMm; }
|
||||
public double getActiveLengthMm() { return activeLengthMm; }
|
||||
public double getActiveLeftInPanelMm() { return activeLeftInPanelMm; }
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* The glyph table is 475 hand-entered figures, and a drawing that renders a character wrong looks
|
||||
* like a rendering bug rather than a bad byte, so the table is checked here instead.
|
||||
*/
|
||||
public class DotMatrixFontTest {
|
||||
|
||||
// where a glyph without a descender sits; the row below it is the descender and cursor row
|
||||
private static final int BASELINE_ROW = DotMatrixFont.GLYPH_HEIGHT - 2;
|
||||
|
||||
private static boolean[][] glyphOf(char c) {
|
||||
boolean[][] dots = new boolean[DotMatrixFont.GLYPH_HEIGHT][DotMatrixFont.GLYPH_WIDTH];
|
||||
for (int row = 0; row < DotMatrixFont.GLYPH_HEIGHT; row++) {
|
||||
for (int column = 0; column < DotMatrixFont.GLYPH_WIDTH; column++) {
|
||||
dots[row][column] = DotMatrixFont.isDotLit(c, row, column);
|
||||
}
|
||||
}
|
||||
return dots;
|
||||
}
|
||||
|
||||
private static boolean isBlank(char c) {
|
||||
for (boolean[] row : glyphOf(c)) {
|
||||
for (boolean lit : row) {
|
||||
if (lit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** A hole in the table would read as a blank cell on the glass, which is what this catches. */
|
||||
@Test
|
||||
public void everyPrintableCharacterHasAGlyph() {
|
||||
for (char c = '!'; c <= '~'; c++) {
|
||||
Assert.assertFalse("'" + c + "' has no glyph", isBlank(c));
|
||||
}
|
||||
Assert.assertTrue("a space must light nothing", isBlank(' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Spot-check against a glyph whose shape is unambiguous: a capital H is two uprights from the top
|
||||
* row down to the baseline, joined by a bar across the middle. A table shifted by one entry fails
|
||||
* here.
|
||||
*/
|
||||
@Test
|
||||
public void theGlyphsAreTheRightWayUpAndRound() {
|
||||
boolean[][] h = glyphOf('H');
|
||||
for (int row = 0; row <= BASELINE_ROW; row++) {
|
||||
Assert.assertTrue("left upright, row " + row, h[row][0]);
|
||||
Assert.assertTrue("right upright, row " + row, h[row][4]);
|
||||
// the bar joins them across the middle row only
|
||||
Assert.assertEquals("middle column, row " + row, row == 3, h[row][2]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The eighth row belongs to the descenders and to the cursor. A capital letter or a digit that
|
||||
* reached into it would collide with the cursor on a real module, and one of the five lower-case
|
||||
* descenders that did not reach it would be the squashed seven-row font this replaced.
|
||||
*/
|
||||
@Test
|
||||
public void onlyTheDescendersReachTheBottomRow() {
|
||||
String descenders = "gjpqy,;_";
|
||||
for (char c = ' '; c <= '~'; c++) {
|
||||
boolean reaches = false;
|
||||
for (int column = 0; column < DotMatrixFont.GLYPH_WIDTH; column++) {
|
||||
reaches |= DotMatrixFont.isDotLit(c, BASELINE_ROW + 1, column);
|
||||
}
|
||||
Assert.assertEquals("'" + c + "' in the bottom row", descenders.indexOf(c) >= 0, reaches);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nothingIsLitOutsideTheGlyph() {
|
||||
for (char c : new char[] {' ', 'A', 'g', '0', '~'}) {
|
||||
Assert.assertFalse("row past the cell",
|
||||
DotMatrixFont.isDotLit(c, DotMatrixFont.GLYPH_HEIGHT, 0));
|
||||
Assert.assertFalse("row above", DotMatrixFont.isDotLit(c, -1, 0));
|
||||
Assert.assertFalse("column past the glyph",
|
||||
DotMatrixFont.isDotLit(c, 0, DotMatrixFont.GLYPH_WIDTH));
|
||||
Assert.assertFalse("column before the glyph", DotMatrixFont.isDotLit(c, 0, -1));
|
||||
}
|
||||
}
|
||||
|
||||
/** Anything the table has no glyph for still has to print something. */
|
||||
@Test
|
||||
public void charactersOutsideTheTableFallBack() {
|
||||
for (char c : new char[] {'\u00e9', '\u0416', (char) 0x1F, (char) 0x7F}) {
|
||||
Assert.assertArrayEquals("'" + c + "' should print as a question mark", glyphOf('?'),
|
||||
glyphOf(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
|
||||
import org.diylc.common.Display;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Folding the label into the character grid, which the character LCD and both graphic panels now
|
||||
* share. A panel shows what fits and nothing else, so the rules about what gets dropped matter.
|
||||
*/
|
||||
public class DotMatrixScreenTest {
|
||||
|
||||
private static List<String> lines(String name, String value, int columns, int rows) {
|
||||
return DotMatrixScreen.layOutText(Display.BOTH, name, value, columns, rows);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textThatFitsIsLeftAlone() {
|
||||
Assert.assertEquals(List.of("LCD1", "16x2", "Parallel HD44780"),
|
||||
lines("LCD1", "16x2, Parallel HD44780", 16, 4));
|
||||
}
|
||||
|
||||
/** The value arrives as one string per property, and each starts its own row. */
|
||||
@Test
|
||||
public void everyPropertyStartsItsOwnRow() {
|
||||
Assert.assertEquals(List.of("0.96\" SSD1306", "128x64", "I2C"),
|
||||
DotMatrixScreen.layOutText(Display.VALUE, "OLED1", "0.96\" SSD1306, 128x64, I2C", 21, 8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aLongLineFoldsAtASpace() {
|
||||
Assert.assertEquals(List.of("LCD1", "Parallel", "HD44780"),
|
||||
lines("LCD1", "Parallel HD44780", 8, 4));
|
||||
}
|
||||
|
||||
/** A word with no space in it has to be cut, or it would run off the glass. */
|
||||
@Test
|
||||
public void aWordTooWideIsCut() {
|
||||
Assert.assertEquals(List.of("ABCDE", "FGHIJ", "KL"),
|
||||
DotMatrixScreen.layOutText(Display.NAME, "ABCDEFGHIJKL", null, 5, 4));
|
||||
}
|
||||
|
||||
/** What runs past the last row is dropped rather than shrunk or scrolled. */
|
||||
@Test
|
||||
public void whatRunsPastTheLastRowIsDropped() {
|
||||
Assert.assertEquals(List.of("LCD1", "16x2"), lines("LCD1", "16x2, Parallel HD44780", 16, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders onto a one-pixel-per-pixel grid and reports the margins the text leaves either side,
|
||||
* which is the only way to see where it actually landed.
|
||||
*/
|
||||
private static int[] margins(List<String> lines, int pixelsX, int pixelsY) {
|
||||
BufferedImage image = new BufferedImage(pixelsX, pixelsY, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
g2d.setColor(Color.WHITE);
|
||||
DotMatrixScreen.drawText(g2d, lines, pixelsX, pixelsY, 0, 0, 1, 1);
|
||||
g2d.dispose();
|
||||
|
||||
int first = -1;
|
||||
int last = -1;
|
||||
for (int x = 0; x < pixelsX; x++) {
|
||||
for (int y = 0; y < pixelsY; y++) {
|
||||
if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
|
||||
first = first == -1 ? x : first;
|
||||
last = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.assertNotEquals("nothing was drawn", -1, first);
|
||||
return new int[] {first, pixelsX - 1 - last};
|
||||
}
|
||||
|
||||
/**
|
||||
* A line that nearly fills the panel used to be pinned against the left edge: centring rounded to
|
||||
* whole cells, and a cell is a glyph plus a blank column, so the slack all ended up on the right.
|
||||
* The 0.96" OLED's own label is exactly that case, 20 characters on a 21-column panel.
|
||||
*/
|
||||
@Test
|
||||
public void aNearlyFullLineIsStillCentred() {
|
||||
int[] m = margins(List.of("0.96\" SSD1306 128x64"), 128, 64);
|
||||
Assert.assertTrue("left " + m[0] + " vs right " + m[1], Math.abs(m[0] - m[1]) <= 1);
|
||||
Assert.assertTrue("should not touch the edge, left margin was " + m[0], m[0] > 0);
|
||||
}
|
||||
|
||||
/** The Nokia's label is 13 characters on a 14-column panel, the same case one column narrower. */
|
||||
@Test
|
||||
public void theNokiaLabelIsCentred() {
|
||||
int[] m = margins(List.of("84x48 PCD8544"), 84, 48);
|
||||
Assert.assertTrue("left " + m[0] + " vs right " + m[1], Math.abs(m[0] - m[1]) <= 1);
|
||||
Assert.assertTrue("should not touch the edge, left margin was " + m[0], m[0] > 0);
|
||||
}
|
||||
|
||||
/** A short line has whole cells to spare, and must come out centred just the same. */
|
||||
@Test
|
||||
public void aShortLineIsCentred() {
|
||||
int[] m = margins(List.of("I2C"), 128, 64);
|
||||
Assert.assertTrue("left " + m[0] + " vs right " + m[1], Math.abs(m[0] - m[1]) <= 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aScreenTurnedOffPrintsNothing() {
|
||||
Assert.assertTrue(
|
||||
DotMatrixScreen.layOutText(Display.NONE, "LCD1", "16x2", 16, 2).isEmpty());
|
||||
}
|
||||
|
||||
/** A grid with no room at all must not throw its way out of a repaint. */
|
||||
@Test
|
||||
public void anEmptyGridPrintsNothing() {
|
||||
Assert.assertTrue(lines("LCD1", "16x2", 0, 2).isEmpty());
|
||||
Assert.assertTrue(lines("LCD1", "16x2", 16, 0).isEmpty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user