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: if 0 <= p.row <= 25:
grid[p.row] = p grid[p.row] = p
# Pass 1: Backgrounds
occlusion_mask = [False] * 40
for r in range(25): for r in range(25):
packet = grid[r] 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() 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 # Default State at start of row
fg = COLORS[7] # White fg = COLORS[7] # White
bg = COLORS[0] # Black bg = COLORS[0] # Black
@@ -229,6 +243,7 @@ class TeletextCanvas(QWidget):
contiguous = True # Mosaic contiguous = True # Mosaic
hold_graphics = False hold_graphics = False
held_char = 0x20 # Space held_char = 0x20 # Space
double_height = False
y = row * self.cell_h y = row * self.cell_h
@@ -248,6 +263,24 @@ class TeletextCanvas(QWidget):
for c in range(40): for c in range(40):
x = c * self.cell_w 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 # Decide byte value
if row == 0 and c < 8: if row == 0 and c < 8:
# Use generated header prefix # Use generated header prefix
@@ -274,9 +307,9 @@ class TeletextCanvas(QWidget):
elif byte_val == 0x1D: # New BG elif byte_val == 0x1D: # New BG
bg = fg bg = fg
elif byte_val == 0x0C: # Normal Height elif byte_val == 0x0C: # Normal Height
pass double_height = False
elif byte_val == 0x0D: # Double Height elif byte_val == 0x0D: # Double Height
pass # Not implemented yet double_height = True
elif byte_val == 0x19: # Contiguous Graphics elif byte_val == 0x19: # Contiguous Graphics
contiguous = True contiguous = True
elif byte_val == 0x1A: # Separated Graphics elif byte_val == 0x1A: # Separated Graphics
@@ -286,43 +319,79 @@ class TeletextCanvas(QWidget):
elif byte_val == 0x1F: # Release Graphics elif byte_val == 0x1F: # Release Graphics
hold_graphics = False 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 # 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 # Draw Foreground
if is_control: if draw_fg:
# "Set-at" spacing attribute? Teletext control codes occupy a space # Calculate height
# unless "Hold Graphics" replaces it with previous graphic char. # For Mosaics, we use the height param.
if hold_graphics and graphics_mode: # For Alphanumerics, we scale the painter.
self.draw_mosaic(painter, x, y, held_char, fg, contiguous)
else: if is_control:
# Draw space (nothing, since we filled BG) # "Set-at" spacing attribute? Teletext control codes occupy a space
pass # unless "Hold Graphics" replaces it with previous graphic char.
else: if hold_graphics and graphics_mode:
if graphics_mode: if double_height:
# Mosaic Graphics self.draw_mosaic(painter, x, y, held_char, fg, contiguous, height=self.cell_h * 2)
if (0x20 <= byte_val <= 0x3F) or (0x60 <= byte_val <= 0x7F): else:
self.draw_mosaic(painter, x, y, byte_val, fg, contiguous) self.draw_mosaic(painter, x, y, held_char, fg, contiguous)
held_char = byte_val
else: 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) char = get_char(byte_val, self.subset_idx)
painter.setPen(fg) painter.setPen(fg)
painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char) if double_height:
held_char = 0x20 painter.save()
else: painter.translate(x, y)
# Alphanumeric painter.scale(1, 2)
char = get_char(byte_val, self.subset_idx) painter.drawText(QRect(0, 0, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
painter.setPen(fg) painter.restore()
painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char) else:
painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
# Draw Cursor # Draw Cursor
# Invert the cell at cursor position # 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) painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Difference)
# Difference with white creates inversion # 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.fillRect(x, y, self.cell_w, self.cell_h, QColor(255, 255, 255))
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver) painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
return next_occlusion_mask
def mousePressEvent(self, event): def mousePressEvent(self, event):
self.setFocus() self.setFocus()
# Calculate cell from mouse position # Calculate cell from mouse position
@@ -360,7 +429,10 @@ class TeletextCanvas(QWidget):
row = int(my / (self.cell_h * scale)) row = int(my / (self.cell_h * scale))
self.set_cursor(col, row) 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 val = char_code & 0x7F
bits = 0 bits = 0
if val >= 0x20: if val >= 0x20:
@@ -368,7 +440,7 @@ class TeletextCanvas(QWidget):
# Grid definitions for 2x3 grid # Grid definitions for 2x3 grid
x_splits = [0, int(self.cell_w / 2), self.cell_w] 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 (col, row) for the 6 bits
block_indices = [ block_indices = [