Fix double height character rendering logic

This commit is contained in:
2026-01-04 18:23:25 +01:00
parent 66cb788fb0
commit 1cdc35850a

View File

@@ -215,13 +215,27 @@ class TeletextCanvas(QWidget):
if 0 <= p.row <= 25:
grid[p.row] = p
# Pass 1: Backgrounds
occlusion_mask = [False] * 40
for r in range(25):
packet = grid[r]
self.draw_row(painter, r, packet)
occlusion_mask = self.draw_row(painter, r, packet, draw_bg=True, draw_fg=False, occlusion_mask=occlusion_mask)
# Pass 2: Foregrounds
occlusion_mask = [False] * 40
for r in range(25):
packet = grid[r]
occlusion_mask = self.draw_row(painter, r, packet, draw_bg=False, draw_fg=True, occlusion_mask=occlusion_mask)
painter.end()
def draw_row(self, painter, row, packet):
def draw_row(self, painter, row, packet, draw_bg=True, draw_fg=True, occlusion_mask=None):
if occlusion_mask is None:
occlusion_mask = [False] * 40
# Output mask for the next row
next_occlusion_mask = [False] * 40
# Default State at start of row
fg = COLORS[7] # White
bg = COLORS[0] # Black
@@ -229,6 +243,7 @@ class TeletextCanvas(QWidget):
contiguous = True # Mosaic
hold_graphics = False
held_char = 0x20 # Space
double_height = False
y = row * self.cell_h
@@ -248,6 +263,24 @@ class TeletextCanvas(QWidget):
for c in range(40):
x = c * self.cell_w
# If this cell is occluded by the row above, skip drawing and attribute processing?
# Spec says "The characters in the row below are ignored."
# Ideally we shouldn't even process attributes, but for simple renderer we just skip draw.
# However, if we skip attribute processing, state (fg/bg) won't update.
# Teletext attributes are serial.
# BUT, if the row above covers it, the viewer sees the row above.
# Does the hidden content affect the *rest* of the row?
# Likely yes, attributes usually propagate.
# But the spec says "ignored". Let's assume we skip *everything* for this cell visually,
# but maybe we should technically maintain state?
# For "Double Height" visual correctness, skipping drawing is the key.
# We will Process attributes (to keep state consistent) but Skip Drawing if occluded.
# Wait, if we process attributes, we might set double_height=True for the NEXT row?
# If this cell is occluded, it shouldn't trigger DH for the next row.
is_occluded = occlusion_mask[c]
# Decide byte value
if row == 0 and c < 8:
# Use generated header prefix
@@ -274,9 +307,9 @@ class TeletextCanvas(QWidget):
elif byte_val == 0x1D: # New BG
bg = fg
elif byte_val == 0x0C: # Normal Height
pass
double_height = False
elif byte_val == 0x0D: # Double Height
pass # Not implemented yet
double_height = True
elif byte_val == 0x19: # Contiguous Graphics
contiguous = True
elif byte_val == 0x1A: # Separated Graphics
@@ -286,43 +319,79 @@ class TeletextCanvas(QWidget):
elif byte_val == 0x1F: # Release Graphics
hold_graphics = False
# Record Double Height for next row
if double_height and not is_occluded:
next_occlusion_mask[c] = True
# If occluded, do not draw anything for this cell
if is_occluded:
continue
# Draw Background
painter.fillRect(x, y, self.cell_w, self.cell_h, bg)
if draw_bg:
# If double height, draw taller background
h_bg = self.cell_h * 2 if double_height else self.cell_h
painter.fillRect(x, y, self.cell_w, h_bg, bg)
# Draw Foreground
if is_control:
# "Set-at" spacing attribute? Teletext control codes occupy a space
# unless "Hold Graphics" replaces it with previous graphic char.
if hold_graphics and graphics_mode:
self.draw_mosaic(painter, x, y, held_char, fg, contiguous)
else:
# Draw space (nothing, since we filled BG)
pass
else:
if graphics_mode:
# Mosaic Graphics
if (0x20 <= byte_val <= 0x3F) or (0x60 <= byte_val <= 0x7F):
self.draw_mosaic(painter, x, y, byte_val, fg, contiguous)
held_char = byte_val
if draw_fg:
# Calculate height
# For Mosaics, we use the height param.
# For Alphanumerics, we scale the painter.
if is_control:
# "Set-at" spacing attribute? Teletext control codes occupy a space
# unless "Hold Graphics" replaces it with previous graphic char.
if hold_graphics and graphics_mode:
if double_height:
self.draw_mosaic(painter, x, y, held_char, fg, contiguous, height=self.cell_h * 2)
else:
self.draw_mosaic(painter, x, y, held_char, fg, contiguous)
else:
# Capital letter in graphics mode? Usually shows char?
# Draw space (nothing, since we filled BG)
pass
else:
if graphics_mode:
# Mosaic Graphics
h_mos = self.cell_h * 2 if double_height else self.cell_h
if (0x20 <= byte_val <= 0x3F) or (0x60 <= byte_val <= 0x7F):
self.draw_mosaic(painter, x, y, byte_val, fg, contiguous, height=h_mos)
held_char = byte_val
else:
# Capital letter in graphics mode? Usually shows char?
char = get_char(byte_val, self.subset_idx)
painter.setPen(fg)
if double_height:
painter.save()
painter.translate(x, y)
painter.scale(1, 2)
painter.drawText(QRect(0, 0, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
painter.restore()
else:
painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
held_char = 0x20
else:
# Alphanumeric
char = get_char(byte_val, self.subset_idx)
painter.setPen(fg)
painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
held_char = 0x20
else:
# Alphanumeric
char = get_char(byte_val, self.subset_idx)
painter.setPen(fg)
painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
if double_height:
painter.save()
painter.translate(x, y)
painter.scale(1, 2)
painter.drawText(QRect(0, 0, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
painter.restore()
else:
painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
# Draw Cursor
# Invert the cell at cursor position
if self.cursor_visible and c == self.cursor_x and row == self.cursor_y:
if draw_fg and self.cursor_visible and c == self.cursor_x and row == self.cursor_y:
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Difference)
# Difference with white creates inversion
# Note: Cursor follows double height? Probably just the active cell.
painter.fillRect(x, y, self.cell_w, self.cell_h, QColor(255, 255, 255))
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
return next_occlusion_mask
def mousePressEvent(self, event):
self.setFocus()
# Calculate cell from mouse position
@@ -360,7 +429,10 @@ class TeletextCanvas(QWidget):
row = int(my / (self.cell_h * scale))
self.set_cursor(col, row)
def draw_mosaic(self, painter, x, y, char_code, color, contiguous):
def draw_mosaic(self, painter, x, y, char_code, color, contiguous, height=None):
if height is None:
height = self.cell_h
val = char_code & 0x7F
bits = 0
if val >= 0x20:
@@ -368,7 +440,7 @@ class TeletextCanvas(QWidget):
# Grid definitions for 2x3 grid
x_splits = [0, int(self.cell_w / 2), self.cell_w]
y_splits = [0, int(self.cell_h / 3), int(2 * self.cell_h / 3), self.cell_h]
y_splits = [0, int(height / 3), int(2 * height / 3), height]
# Block indices (col, row) for the 6 bits
block_indices = [