diff --git a/decode.cpp b/decode.cpp new file mode 100644 index 0000000..f9b254d --- /dev/null +++ b/decode.cpp @@ -0,0 +1,992 @@ +/* + * Copyright (C) 2020-2022 Gavin MacGregor + * + * This file is part of QTeletextMaker. + * + * QTeletextMaker 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. + * + * QTeletextMaker 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 QTeletextMaker. If not, see . + */ + +#include +//#include +#include +#include +#include + +#include "decode.h" + +TeletextPageDecode::TeletextPageDecode() +{ + m_renderLevel = 0; + + for (int r=0; r<25; r++) + for (int c=0; c<72; c++) + m_refresh[r][c] = false; + + m_finalFullScreenColour = 0; + m_finalFullScreenQColor.setRgb(0, 0, 0); + for (int r=0; r<25; r++) { + m_fullRowColour[r] = 0; + m_fullRowQColor[r].setRgb(0, 0, 0); + } + m_leftSidePanelColumns = m_rightSidePanelColumns = 0; + m_textLayer.push_back(&m_level1Layer); + m_textLayer.push_back(new EnhanceLayer); +} + +TeletextPageDecode::~TeletextPageDecode() +{ + while (m_textLayer.size()>1) { + delete m_textLayer.back(); + m_textLayer.pop_back(); + } +} + +void TeletextPageDecode::setRefresh(int r, int c, bool refresh) +{ + m_refresh[r][c] = refresh; +} + +void TeletextPageDecode::setTeletextPage(LevelOnePage *newCurrentPage) +{ + m_levelOnePage = newCurrentPage; + m_level1Layer.setTeletextPage(newCurrentPage); + updateSidePanels(); +} + +void TeletextPageDecode::setRenderLevel(int newRenderLevel) +{ + if (newRenderLevel == m_renderLevel) + return; + m_renderLevel = newRenderLevel; + decodePage(); +} + +void TeletextPageDecode::updateSidePanels() +{ + int oldLeftSidePanelColumns = m_leftSidePanelColumns; + int oldRightSidePanelColumns = m_rightSidePanelColumns; + + if (m_renderLevel >= (3-m_levelOnePage->sidePanelStatusL25()) && m_levelOnePage->leftSidePanelDisplayed()) + m_leftSidePanelColumns = (m_levelOnePage->sidePanelColumns() == 0) ? 16 : m_levelOnePage->sidePanelColumns(); + else + m_leftSidePanelColumns = 0; + + if (m_renderLevel >= (3-m_levelOnePage->sidePanelStatusL25()) && m_levelOnePage->rightSidePanelDisplayed()) + m_rightSidePanelColumns = 16-m_levelOnePage->sidePanelColumns(); + else + m_rightSidePanelColumns = 0; + + if (m_leftSidePanelColumns != oldLeftSidePanelColumns || m_rightSidePanelColumns != oldRightSidePanelColumns) { + emit sidePanelsChanged(); + decodePage(); + } +} + +void TeletextPageDecode::buildEnhanceMap(TextLayer *enhanceLayer, int tripletNumber) +{ + bool terminatorFound=false; + ActivePosition activePosition; + const X26Triplet *x26Triplet; + int originModifierR=0; + int originModifierC=0; + + do { + x26Triplet = &m_levelOnePage->enhancements()->at(tripletNumber); + if (x26Triplet->isRowTriplet()) + // Row address group + switch (x26Triplet->mode()) { + case 0x00: // Full screen colour + if (m_renderLevel >= 2 && ((x26Triplet->data() & 0x60) == 0x00) && !activePosition.isDeployed()) + enhanceLayer->setFullScreenColour(x26Triplet->data()); + break; + case 0x01: // Full row colour + if (m_renderLevel >= 2 && activePosition.setRow(x26Triplet->addressRow()) && ((x26Triplet->data() & 0x60) == 0x00 || (x26Triplet->data() & 0x60) == 0x60)) + enhanceLayer->setFullRowColour(activePosition.row(), x26Triplet->data() & 0x1f, (x26Triplet->data() & 0x60) == 0x60); + break; + case 0x04: // Set active position + if (activePosition.setRow(x26Triplet->addressRow()) && m_renderLevel >= 2 && x26Triplet->data() < 40) + activePosition.setColumn(x26Triplet->data()); + break; + case 0x07: // Address row 0 + if (x26Triplet->address() == 0x3f && !activePosition.isDeployed()) { + activePosition.setRow(0); + activePosition.setColumn(8); + if (m_renderLevel >= 2 && ((x26Triplet->data() & 0x60) == 0x00 || (x26Triplet->data() & 0x60) == 0x60)) + enhanceLayer->setFullRowColour(0, x26Triplet->data() & 0x1f, (x26Triplet->data() & 0x60) == 0x60); + } + break; + case 0x10: // Origin modifier + if (m_renderLevel >= 2 && (tripletNumber+1) < m_levelOnePage->enhancements()->size() && m_levelOnePage->enhancements()->at(tripletNumber+1).mode() >= 0x11 && m_levelOnePage->enhancements()->at(tripletNumber+1).mode() <= 0x13 && x26Triplet->address() >= 40 && x26Triplet->data() < 72) { + originModifierR = x26Triplet->address()-40; + originModifierC = x26Triplet->data(); + } + break; + case 0x11 ... 0x13: // Invoke Object + if (m_renderLevel >= 2) { + if ((x26Triplet->address() & 0x18) == 0x08) { + // Local Object + // Check if the pointer in the Invocation triplet is valid + // Can't point to triplets 13-15; only triplets 0-12 per packet + if ((x26Triplet->data() & 0x0f) > 12) + break; + int tripletPointer = ((x26Triplet->data() >> 4) | ((x26Triplet->address() & 1) << 3)) * 13 + (x26Triplet->data() & 0x0f); + // Can't point to triplet beyond the end of the Local Enhancement Data + if ((tripletPointer+1) >= m_levelOnePage->enhancements()->size()) + break; + // Check if we're pointing to an actual Object Definition of the same type + if ((x26Triplet->mode() | 0x04) != m_levelOnePage->enhancements()->at(tripletPointer).mode()) + break; + // The Object Definition can't declare it's at triplet 13-15; only triplets 0-12 per packet + if ((m_levelOnePage->enhancements()->at(tripletPointer).data() & 0x0f) > 12) + break; + // Check if the Object Definition triplet is where it declares it is + if ((((m_levelOnePage->enhancements()->at(tripletPointer).data() >> 4) | ((m_levelOnePage->enhancements()->at(tripletPointer).address() & 1) << 3)) * 13 + (m_levelOnePage->enhancements()->at(tripletPointer).data() & 0x0f)) != tripletPointer) + break; + // Check if (sub)Object type can be invoked by Object type we're within + if (enhanceLayer->objectType() >= (x26Triplet->mode() & 0x03)) + break; + // Is the object required at the current presentation Level? + if (m_renderLevel == 2 && (m_levelOnePage->enhancements()->at(tripletPointer).address() & 0x08) == 0x00) + break; + if (m_renderLevel == 3 && (m_levelOnePage->enhancements()->at(tripletPointer).address() & 0x10) == 0x00) + break; + EnhanceLayer *newLayer = new EnhanceLayer; + m_textLayer.push_back(newLayer); + newLayer->setObjectType(x26Triplet->mode() & 0x03); + newLayer->setOrigin(enhanceLayer->originR() + activePosition.row() + originModifierR, enhanceLayer->originC() + activePosition.column() + originModifierC); + buildEnhanceMap(newLayer, tripletPointer+1); + } else + qDebug("POP or GPOP"); + originModifierR = originModifierC = 0; + } + break; + case 0x15 ... 0x17: // Define Object, also used as terminator + terminatorFound = true; + break; + case 0x1f: // Terminator + if (x26Triplet->address() == 63) + terminatorFound = true; + break; + } + else { + // Column address group + bool columnTripletActioned = true; + switch (x26Triplet->mode()) { + // First we deal with column triplets that are also valid at Level 1.5 + case 0x0b: // G3 mosaic character at Level 2.5 + if (m_renderLevel <= 1) + break; + // fall-through + case 0x02: // G3 mosaic character at Level 1.5 + case 0x0f: // G2 character + case 0x10 ... 0x1f: // Diacritical mark + if (activePosition.setColumn(x26Triplet->addressColumn()) && x26Triplet->data() >= 0x20) + enhanceLayer->enhanceMap.insert(qMakePair(activePosition.row(), activePosition.column()), qMakePair(x26Triplet->mode() | 0x20, x26Triplet->data())); + break; + // Make sure that PDC and reserved triplets don't affect the Active Position + case 0x04 ... 0x06: // 0x04 and 0x05 are reserved, 0x06 for PDC + case 0x0a: // Reserved + break; + default: + columnTripletActioned = false; + } + // All remaining possible column triplets at Level 2.5 affect the Active Position + if (m_renderLevel >= 2 && !columnTripletActioned && activePosition.setColumn(x26Triplet->addressColumn())) + enhanceLayer->enhanceMap.insert(qMakePair(activePosition.row(), activePosition.column()), qMakePair(x26Triplet->mode() | 0x20, x26Triplet->data())); + } + tripletNumber++; + } while (!terminatorFound && tripletNumber < m_levelOnePage->enhancements()->size()); +} + +void TeletextPageDecode::decodePage() +{ + int currentFullRowColour, downwardsFullRowColour; + int renderedFullScreenColour; + struct { + bool operator() (TextLayer *i, TextLayer *j) { return (i->objectType() < j->objectType()); } + } compareLayer; + +// QTime renderPageTime; + +// renderPageTime.start(); + + updateSidePanels(); + + while (m_textLayer.size()>2) { + delete m_textLayer.back(); + m_textLayer.pop_back(); + } + + renderedFullScreenColour = (m_renderLevel >= 2) ? m_levelOnePage->defaultScreenColour() : 0; + downwardsFullRowColour = (m_renderLevel >= 2) ? m_levelOnePage->defaultRowColour() : 0; + setFullScreenColour(renderedFullScreenColour); + for (int r=0; r<25; r++) + setFullRowColour(r, downwardsFullRowColour); + + m_textLayer[1]->enhanceMap.clear(); + + if (m_renderLevel > 0 && !m_levelOnePage->enhancements()->isEmpty()) { + m_textLayer[1]->setFullScreenColour(-1); + for (int r=0; r<25; r++) + m_textLayer[1]->setFullRowColour(r, -1, false); + buildEnhanceMap(m_textLayer[1]); + + if (m_textLayer.size() > 2) + std::stable_sort(m_textLayer.begin()+2, m_textLayer.end(), compareLayer); + + if (m_renderLevel >= 2) { + if (m_textLayer[1]->fullScreenColour() != -1) + downwardsFullRowColour = m_textLayer[1]->fullScreenColour(); + for (int r=0; r<25; r++) { + for (int l=0; l<2; l++) { + if (r == 0 && m_textLayer[l]->fullScreenColour() != - 1) + renderedFullScreenColour = m_textLayer[l]->fullScreenColour(); + if (m_textLayer[l]->fullRowColour(r) == - 1) + currentFullRowColour = downwardsFullRowColour; + else { + currentFullRowColour = m_textLayer[l]->fullRowColour(r); + if (m_textLayer[l]->fullRowDownwards(r)) + downwardsFullRowColour = currentFullRowColour; + } + } + setFullRowColour(r ,currentFullRowColour); + } + setFullScreenColour(renderedFullScreenColour); + } + } + + for (int r=0; r<25; r++) + decodeRow(r); +// qDebug("Full page render: %d ms", renderPageTime.elapsed()); +} + +void TeletextPageDecode::decodeRow(int r) +{ + int c; + int phaseNumberRender = 0; + bool decodeNextRow = false; + bool applyRightHalf = false; + bool previouslyDoubleHeight, previouslyBottomHalf, underlined; + bool doubleHeightFound = false; + textCharacter resultCharacter, layerCharacter; + applyAttributes layerApplyAttributes; + textAttributes underlyingAttributes, resultAttributes; + int level1CharSet; + + for (c=0; c<72; c++) { + textCell oldTextCell = m_cell[r][c]; + + resultAttributes = underlyingAttributes; + for (int l=0; lcharacter(r, c); + if (layerCharacter.code != 0x00) + resultCharacter = layerCharacter; + if (l == 0) { +// m_cell[r][c].level1Mosaic = (resultCharacter.set == 24 || resultCharacter.set == 25) && m_levelOnePage->character(r, c) >= 0x20; + m_cell[r][c].level1Mosaic = (resultCharacter.set == 24 || resultCharacter.set == 25); + if (!m_cell[r][c].level1Mosaic) + level1CharSet = resultCharacter.set; + m_cell[r][c].level1CharSet = level1CharSet; + } + + layerApplyAttributes = { false, false, false, false, false, false, false, false }; + m_textLayer[l]->attributes(r, c, &layerApplyAttributes); + if (layerApplyAttributes.copyAboveAttributes) { + resultAttributes = m_cell[r-1][c].attribute; + layerApplyAttributes.copyAboveAttributes = false; + break; + } + if (layerApplyAttributes.applyForeColour) { + resultAttributes.foreColour = layerApplyAttributes.attribute.foreColour; + if (l == 0 && m_renderLevel >= 2) + resultAttributes.foreColour |= m_foregroundRemap[m_levelOnePage->colourTableRemap()]; + } + if (layerApplyAttributes.applyBackColour) { + resultAttributes.backColour = layerApplyAttributes.attribute.backColour; + if (l == 0) { + if (m_renderLevel >= 2) + if (resultAttributes.backColour == 0x20) + resultAttributes.backColour = (c > 39 || m_levelOnePage->blackBackgroundSubst()) ? m_fullRowColour[r] : m_backgroundRemap[m_levelOnePage->colourTableRemap()]; + else + resultAttributes.backColour |= m_backgroundRemap[m_levelOnePage->colourTableRemap()]; + else + if (resultAttributes.backColour == 0x20) + resultAttributes.backColour = 0x00; + } + } + + if (layerApplyAttributes.applyFlash) { + //BUG Adaptive Objects disrupt inc/dec flash + resultAttributes.flash = layerApplyAttributes.attribute.flash; + if (resultAttributes.flash.mode != 0) + phaseNumberRender = (resultAttributes.flash.ratePhase == 4 || resultAttributes.flash.ratePhase == 5) ? 1 : resultAttributes.flash.ratePhase; + } + if (layerApplyAttributes.applyDisplayAttributes) + resultAttributes.display = layerApplyAttributes.attribute.display; + else { + // Selecting contiguous mosaics wih a triplet will override an earlier Level 1 separated mosaics attribute until a further Level 1 contiguous mosaic attribute is encountered + resultAttributes.display.forceContiguous = (layerApplyAttributes.applyContiguousOnly) ? false : underlyingAttributes.display.forceContiguous; + if (layerApplyAttributes.applyTextSizeOnly) { + resultAttributes.display.doubleHeight = layerApplyAttributes.attribute.display.doubleHeight; + resultAttributes.display.doubleWidth = layerApplyAttributes.attribute.display.doubleWidth; + } + if (layerApplyAttributes.applyBoxingOnly) + resultAttributes.display.boxingWindow = layerApplyAttributes.attribute.display.boxingWindow; + if (layerApplyAttributes.applyConcealOnly || layerApplyAttributes.applyForeColour) + resultAttributes.display.conceal = layerApplyAttributes.attribute.display.conceal; + } + if (m_textLayer[l]->objectType() <= 1) + underlyingAttributes = resultAttributes; + + if (m_renderLevel == 0) + break; + } + + underlined = false; + if (resultAttributes.display.underlineSeparated) { + if (resultCharacter.set == 24) + resultCharacter.set = 25; + else + underlined = resultCharacter.set < 24; + } + if (resultAttributes.display.forceContiguous && resultCharacter.set == 25) + resultCharacter.set = 24; + + resultAttributes.flash.phaseNumber = phaseNumberRender; + + previouslyDoubleHeight = m_cell[r][c].attribute.display.doubleHeight; + previouslyBottomHalf = m_cell[r][c].bottomHalf; + + m_cell[r][c].character = resultCharacter; + m_cell[r][c].attribute = resultAttributes; + + if (m_cell[r][c] != oldTextCell) { + m_refresh[r][c] = true; + + if (static_cast(m_textLayer[0])->rowHeight(r) == Level1Layer::TopHalf) { + m_refresh[r+1][c] = true; + decodeNextRow = true; + } + + if ((m_cell[r][c].attribute.display.doubleHeight || oldTextCell.attribute.display.doubleHeight) && r < 25) + m_refresh[r+1][c] = true; + if ((m_cell[r][c].attribute.display.doubleWidth || oldTextCell.attribute.display.doubleWidth) && c < 72) + m_refresh[r][c+1] = true; + if (((m_cell[r][c].attribute.display.doubleHeight && m_cell[r][c].attribute.display.doubleWidth) || (oldTextCell.attribute.display.doubleHeight && oldTextCell.attribute.display.doubleWidth)) && r < 25 && c < 72) + m_refresh[r+1][c+1] = true; + } + + if (resultAttributes.flash.ratePhase == 4 && ++phaseNumberRender == 4) + phaseNumberRender = 1; + if (resultAttributes.flash.ratePhase == 5 && --phaseNumberRender == 0) + phaseNumberRender = 3; + + if (r > 0) + m_cell[r][c].bottomHalf = m_cell[r-1][c].attribute.display.doubleHeight && !m_cell[r-1][c].bottomHalf; + if ((resultAttributes.display.doubleHeight != previouslyDoubleHeight) || (m_cell[r][c].bottomHalf != previouslyBottomHalf)) + decodeNextRow = true; + m_cell[r][c].rightHalf = applyRightHalf; + + if (resultAttributes.display.doubleHeight) + doubleHeightFound = true; + if (resultAttributes.display.doubleWidth || (m_cell[r][c].bottomHalf && c > 0 && m_cell[r-1][c-1].rightHalf)) + applyRightHalf ^= true; + else + applyRightHalf = false; + } + + if (decodeNextRow && r<24) + decodeRow(r+1); +} + +textCell& TeletextPageDecode::cellAtCharacterOrigin(int r, int c) +{ +/* if (m_cell[r][c].bottomHalf && r > 0) { + if (m_cell[r][c].rightHalf && c > 0) + // Double size + return m_cell[r-1][c-1]; + else + // Double height + return m_cell[r-1][c]; + } else { + if (m_cell[r][c].rightHalf && c > 0) + // Double width + return m_cell[r][c-1]; + else + // Normal size + return m_cell[r][c]; + }*/ + switch (cellCharacterFragment(r, c)) { + case TeletextPageDecode::DoubleHeightBottomHalf: + case TeletextPageDecode::DoubleSizeBottomLeftQuarter: + return m_cell[r-1][c]; + case TeletextPageDecode::DoubleWidthRightHalf: + case TeletextPageDecode::DoubleSizeTopRightQuarter: + return m_cell[r][c-1]; + case TeletextPageDecode::DoubleSizeBottomRightQuarter: + return m_cell[r-1][c-1]; + default: + return m_cell[r][c]; + } +} + +QColor TeletextPageDecode::cellQColor(int r, int c, ColourPart colourPart) +{ + const textCell& cell = cellAtCharacterOrigin(r, c); + const bool newsFlashOrSubtitle = m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle); + int resultCLUT; + + switch (colourPart) { + case Foreground: + if (!cell.attribute.display.invert) + resultCLUT = cell.attribute.foreColour; + else + resultCLUT = cell.attribute.backColour; + break; + case Background: + if (!cell.attribute.display.invert) + resultCLUT = cell.attribute.backColour; + else + resultCLUT = cell.attribute.foreColour; + break; + case FlashForeground: + if (!cell.attribute.display.invert) + resultCLUT = cell.attribute.foreColour ^ 8; + else + resultCLUT = cell.attribute.backColour ^ 8; + break; + } + + if (resultCLUT == 8) { + // Transparent CLUT - either Full Row Colour or Video + // Logic of table C.1 in spec implemented to find out which it is + if (cell.attribute.display.boxingWindow != newsFlashOrSubtitle) + return QColor(Qt::transparent); + + int rowColour; + + if (cellCharacterFragment(r, c) == TeletextPageDecode::DoubleHeightBottomHalf || + cellCharacterFragment(r, c) == TeletextPageDecode::DoubleSizeBottomLeftQuarter || + cellCharacterFragment(r, c) == TeletextPageDecode::DoubleSizeBottomRightQuarter) + rowColour = m_fullRowColour[r-1]; + else + rowColour = m_fullRowColour[r]; + + if (rowColour == 8) + return QColor(Qt::transparent); + else + return m_levelOnePage->CLUTtoQColor(rowColour, m_renderLevel); + } else if (!cell.attribute.display.boxingWindow && newsFlashOrSubtitle) + return QColor(Qt::transparent); + + return m_levelOnePage->CLUTtoQColor(resultCLUT, m_renderLevel); +} + +QColor TeletextPageDecode::cellForegroundQColor(int r, int c) +{ + return cellQColor(r, c, Foreground); +} + +QColor TeletextPageDecode::cellBackgroundQColor(int r, int c) +{ + return cellQColor(r, c, Background); +} + +QColor TeletextPageDecode::cellFlashForegroundQColor(int r, int c) +{ + return cellQColor(r, c, FlashForeground); +} + +TeletextPageDecode::CharacterFragment TeletextPageDecode::cellCharacterFragment(int r, int c) const +{ + if (m_cell[r][c].bottomHalf && r > 0) { + if (m_cell[r][c].rightHalf && c > 0) + return CharacterFragment::DoubleSizeBottomRightQuarter; + else if (m_cell[r-1][c].attribute.display.doubleWidth) + return CharacterFragment::DoubleSizeBottomLeftQuarter; + else + return CharacterFragment::DoubleHeightBottomHalf; + } else if (m_cell[r][c].rightHalf && c > 0) { + if (m_cell[r][c-1].attribute.display.doubleHeight) + return CharacterFragment::DoubleSizeTopRightQuarter; + else + return CharacterFragment::DoubleWidthRightHalf; + } + + if (m_cell[r][c].attribute.display.doubleHeight) { + if (m_cell[r][c].attribute.display.doubleWidth) + return CharacterFragment::DoubleSizeTopLeftQuarter; + else + return CharacterFragment::DoubleHeightTopHalf; + } else if (m_cell[r][c].attribute.display.doubleWidth) + return CharacterFragment::DoubleWidthLeftHalf; + + return CharacterFragment::NormalSize; +} + +inline void TeletextPageDecode::setFullScreenColour(int newColour) +{ + if (newColour == 8 || m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle)) { + m_finalFullScreenQColor = QColor(0, 0, 0, 0); + emit fullScreenColourChanged(QColor(0, 0, 0, 0)); + return; + } + QColor newFullScreenQColor = m_levelOnePage->CLUTtoQColor(newColour, m_renderLevel); + m_finalFullScreenColour = newColour; + if (m_finalFullScreenQColor != newFullScreenQColor) { + m_finalFullScreenQColor = newFullScreenQColor; + emit fullScreenColourChanged(m_finalFullScreenQColor); + } +} + +inline void TeletextPageDecode::setFullRowColour(int row, int newColour) +{ + m_fullRowColour[row] = newColour; + + if (newColour == 8 || m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle)) { + m_fullRowQColor[row] = QColor(0, 0, 0, 0); + emit fullRowColourChanged(row, QColor(0, 0, 0, 0)); + return; + } + QColor newFullRowQColor = m_levelOnePage->CLUTtoQColor(newColour, m_renderLevel); + if (m_fullRowQColor[row] != newFullRowQColor) { + for (int c=0; c<72; c++) { + if (m_cell[row][c].attribute.foreColour == 8 || m_cell[row][c].attribute.backColour == 8) + setRefresh(row, c, true); + } + m_fullRowQColor[row] = newFullRowQColor; + emit fullRowColourChanged(row, m_fullRowQColor[row]); + } +} + +void TextLayer::setTeletextPage(LevelOnePage *newCurrentPage) { m_levelOnePage = newCurrentPage; } +void TextLayer::setFullScreenColour(int newColour) { m_layerFullScreenColour = newColour; } + +void TextLayer::setFullRowColour(int r, int newColour, bool newDownwards) +{ + m_layerFullRowColour[r] = newColour; + m_layerFullRowDownwards[r] = newDownwards; +} + +void EnhanceLayer::setObjectType(int newObjectType) { m_objectType = newObjectType; } + +void EnhanceLayer::setOrigin(int r, int c) +{ + m_originR = r; + m_originC = c; +} + +Level1Layer::Level1Layer() +{ + for (int r=0; r<25; r++) { + m_rowHasDoubleHeightAttr[r] = false; + m_rowHeight[r] = Normal; + } +} + +EnhanceLayer::EnhanceLayer() +{ + for (int r=0; r<25; r++) { + m_layerFullRowColour[r] = -1; + m_layerFullRowDownwards[r] = false; + } +} + +textCharacter EnhanceLayer::character(int r, int c) +{ + r -= m_originR; + c -= m_originC; + if (r < 0 || c < 0) + return { 0, 0 }; + + // QPair.first is triplet mode, QPair.second is triplet data + QList> enhancements = enhanceMap.values(qMakePair(r, c)); + + if (enhancements.size() > 0) + for (int i=0; i= 0x20) + return { enhancements.at(i).second, (enhancements.at(i).second & 0x20) ? 24 : 0 }; + } + return { 0, 0 }; +} + +void EnhanceLayer::attributes(int r, int c, applyAttributes *layerApplyAttributes) +{ + r -= m_originR; + c -= m_originC; + if (r < 0 || c < 0) + return; + if (m_objectType == 2) { + // Adaptive Object - find rightmost column addressed on this row if we haven't already + if (r != m_rowCached) { + m_rightMostColumn[r] = 0; + m_rowCached = r; + for (int cc=39; cc>0; cc--) + if (enhanceMap.contains(qMakePair(r, cc))) { + m_rightMostColumn[r] = cc; + break; + } + } + // On new row, default to attributes already on page + // At end of rightmost column, let go of all attributes + if (c == 0 || c == m_rightMostColumn[r]+1) + m_applyAttributes = { false, false, false, false, false, false, false, false }; + else { + // Re-apply attributes that Object has defined previously on this row + if (m_applyAttributes.applyForeColour) { + layerApplyAttributes->applyForeColour = true; + layerApplyAttributes->attribute.foreColour = m_applyAttributes.attribute.foreColour; + } + if (m_applyAttributes.applyBackColour) { + layerApplyAttributes->applyBackColour = true; + layerApplyAttributes->attribute.backColour = m_applyAttributes.attribute.backColour; + } + //BUG Adaptive Objects disrupt inc/dec flash + if (m_applyAttributes.applyFlash) { + layerApplyAttributes->applyFlash = true; + layerApplyAttributes->attribute.flash.mode = m_applyAttributes.attribute.flash.mode; + layerApplyAttributes->attribute.flash.ratePhase = m_applyAttributes.attribute.flash.ratePhase; + } + if (m_applyAttributes.applyDisplayAttributes) { + layerApplyAttributes->applyDisplayAttributes = true; + layerApplyAttributes->attribute.display = m_applyAttributes.attribute.display; + } + } + } + if (m_objectType == 3) { + if (r == 0 && c == 0) { + // Passive Objects always start with all these default attributes + m_applyAttributes.applyForeColour = true; + m_applyAttributes.attribute.foreColour = 0x07; + m_applyAttributes.applyBackColour = true; + m_applyAttributes.attribute.backColour = 0x00; + m_applyAttributes.applyDisplayAttributes = true; + m_applyAttributes.applyFlash = true; + m_applyAttributes.attribute.flash.mode = 0; + m_applyAttributes.attribute.flash.ratePhase = 0; + m_applyAttributes.attribute.display.doubleHeight = false; + m_applyAttributes.attribute.display.doubleWidth = false; + m_applyAttributes.attribute.display.boxingWindow = false; + m_applyAttributes.attribute.display.conceal = false; + m_applyAttributes.attribute.display.invert = false; + m_applyAttributes.attribute.display.underlineSeparated = false; + m_applyAttributes.attribute.display.forceContiguous = false; + } + if (character(r+m_originR, c+m_originC).code == 0x00) + // Passive Object attributes only apply where it also defines a character + // In this case, wrench the pointer-parameter to alter only the attributes of the Object + layerApplyAttributes = &m_applyAttributes; + else + *layerApplyAttributes = m_applyAttributes; + } + + // QPair.first is triplet mode, QPair.second is triplet data + QList> enhancements = enhanceMap.values(qMakePair(r, c)); + + for (int i=0; iapplyForeColour = true; + layerApplyAttributes->attribute.foreColour = enhancements.at(i).second; + } + break; + case 0x23: // Background colour + if ((enhancements.at(i).second & 0x60) == 0) { + layerApplyAttributes->applyBackColour = true; + layerApplyAttributes->attribute.backColour = enhancements.at(i).second; + } + break; + case 0x27: // Additional flash functions + if ((enhancements.at(i).second & 0x60) == 0 && (enhancements.at(i).second & 0x18) != 0x18) { // Avoid reserved rate/phase + layerApplyAttributes->applyFlash = true; + layerApplyAttributes->attribute.flash.mode = enhancements.at(i).second & 0x03; + layerApplyAttributes->attribute.flash.ratePhase = (enhancements.at(i).second >> 2) & 0x07; + } + break; + case 0x2c: // Display attributes + layerApplyAttributes->applyDisplayAttributes = true; + layerApplyAttributes->attribute.display.doubleHeight = enhancements.at(i).second & 0x01; + layerApplyAttributes->attribute.display.boxingWindow = enhancements.at(i).second & 0x02; + layerApplyAttributes->attribute.display.conceal = enhancements.at(i).second & 0x04; + layerApplyAttributes->attribute.display.invert = enhancements.at(i).second & 0x10; + layerApplyAttributes->attribute.display.underlineSeparated = enhancements.at(i).second & 0x20; + // Selecting contiguous mosaics wih a triplet will override an earlier Level 1 separated mosaics attribute + layerApplyAttributes->attribute.display.forceContiguous = !layerApplyAttributes->attribute.display.underlineSeparated; + layerApplyAttributes->attribute.display.doubleWidth = enhancements.at(i).second & 0x40; + break; + } + if (m_objectType >= 2) + m_applyAttributes = *layerApplyAttributes; +} + + +void Level1Layer::updateRowCache(int r) +{ + level1CacheAttributes buildCacheAttributes; + bool doubleHeightAttrFound = false; + + for (int c=0; c<40; c++) { + unsigned char charCode = m_levelOnePage->character(r, c); + // Set at spacing attributes + switch (charCode) { + case 0x0c: // Normal size + if (buildCacheAttributes.sizeCode != 0x0c) // Size CHANGE resets held mosaic to space + buildCacheAttributes.holdChar = 0x20; + buildCacheAttributes.sizeCode = 0x0c; + break; + case 0x19: // Contiguous mosaics + buildCacheAttributes.separated = false; + break; + case 0x1a: // Separated mosaics + buildCacheAttributes.separated = true; + break; + case 0x1c: // Black background + buildCacheAttributes.backColour = 0x00; + break; + case 0x1d: // New background + buildCacheAttributes.backColour = buildCacheAttributes.foreColour & 0x07; + break; + case 0x1e: // Hold mosaics + buildCacheAttributes.held = true; + break; + } + + if (buildCacheAttributes.mosaics && (charCode & 0x20)) { + buildCacheAttributes.holdChar = charCode; + buildCacheAttributes.holdSeparated = buildCacheAttributes.separated; + } + + m_attributeCache[c] = buildCacheAttributes; + + // Set-after spacing attributes + switch (charCode) { + case 0x00 ... 0x07: // Alphanumeric + foreground colour + buildCacheAttributes.foreColour = charCode; + buildCacheAttributes.mosaics = false; + buildCacheAttributes.holdChar = 0x20; // Switch from mosaics to alpha resets held mosaic + buildCacheAttributes.holdSeparated = false; + break; + case 0x10 ... 0x17: // Mosaic + foreground colour + buildCacheAttributes.foreColour = charCode & 0x07; + buildCacheAttributes.mosaics = true; + break; + case 0x0d: // Double height + case 0x0f: // Double size + doubleHeightAttrFound = true; + // fall-through + case 0x0e: // Double width + if (buildCacheAttributes.sizeCode != charCode) // Size CHANGE resets held mosaic to space + buildCacheAttributes.holdChar = 0x20; + buildCacheAttributes.sizeCode = charCode; + break; + case 0x1b: // ESC/switch + buildCacheAttributes.escSwitch ^= true; + break; + case 0x1f: // Release mosaics + buildCacheAttributes.held = false; + break; + } + } + + if (doubleHeightAttrFound != m_rowHasDoubleHeightAttr[r]) { + m_rowHasDoubleHeightAttr[r] = doubleHeightAttrFound; + for (int dr=r; dr<24; dr++) + if (m_rowHasDoubleHeightAttr[dr]) { + m_rowHeight[dr] = TopHalf; + m_rowHeight[++dr] = BottomHalf; + } else + m_rowHeight[dr] = Normal; + } +} + +textCharacter Level1Layer::character(int r, int c) +{ + textCharacter result; + + if (r != m_rowCached) + updateRowCache(r); + if (c > 39 || m_rowHeight[r] == BottomHalf) + return { 0x20, 0 }; + result.code = m_levelOnePage->character(r, c); + if (m_levelOnePage->secondCharSet() != 0xf && m_attributeCache[c].escSwitch) + result.set = g0CharacterMap.value(((m_levelOnePage->secondCharSet() << 3) | m_levelOnePage->secondNOS()), 0); + else + result.set = g0CharacterMap.value(((m_levelOnePage->defaultCharSet() << 3) | m_levelOnePage->defaultNOS()), 0); + if (result.code < 0x20) { + result.code = m_attributeCache[c].held ? m_attributeCache[c].holdChar : 0x20; + if (m_attributeCache[c].held && c > 0) + result.set = 24+m_attributeCache[c].holdSeparated; +// else +// result.set = m_attributeCache[c].mosaics*24; + } else if (m_attributeCache[c].mosaics && (result.code & 0x20)) + result.set = 24+m_attributeCache[c].separated; + return result; +} + +void Level1Layer::attributes(int r, int c, applyAttributes *layerApplyAttributes) +{ + unsigned char characterCode; + + if (m_rowHeight[r] == BottomHalf) { + layerApplyAttributes->copyAboveAttributes = true; + return; + } + if (r != m_rowCached) + updateRowCache(r); + if (c == 0 || c == 40 || c == 56) { + // Start of row default conditions, also when crossing into side panels + layerApplyAttributes->applyForeColour = true; + layerApplyAttributes->attribute.foreColour = 0x07; + layerApplyAttributes->applyBackColour = true; + layerApplyAttributes->attribute.backColour = 0x20; + layerApplyAttributes->applyDisplayAttributes = true; + layerApplyAttributes->applyFlash = true; + layerApplyAttributes->attribute.flash.mode = 0; + layerApplyAttributes->attribute.flash.ratePhase = 0; + layerApplyAttributes->attribute.display.doubleHeight = false; + layerApplyAttributes->attribute.display.doubleWidth = false; + layerApplyAttributes->attribute.display.boxingWindow = false; + layerApplyAttributes->attribute.display.conceal = false; + layerApplyAttributes->attribute.display.invert = false; + layerApplyAttributes->attribute.display.underlineSeparated = false; + layerApplyAttributes->attribute.display.forceContiguous = false; + //TODO fontstyle + } + if (c > 39) + return; + if (c > 0) { + // Set-after + characterCode = m_levelOnePage->character(r, c-1); + switch (characterCode) { + case 0x00 ... 0x07: // Alphanumeric + Foreground colour + case 0x10 ... 0x17: // Mosaic + Foreground colour + layerApplyAttributes->applyForeColour = true; + layerApplyAttributes->attribute.foreColour = characterCode & 0x07; + layerApplyAttributes->attribute.display.conceal = false; + break; + case 0x08: // Flashing + layerApplyAttributes->applyFlash = true; + layerApplyAttributes->attribute.flash.mode = 1; + layerApplyAttributes->attribute.flash.ratePhase = 0; + break; + case 0x0a: // End box + if (m_levelOnePage->character(r, c) == 0x0a) { + layerApplyAttributes->applyBoxingOnly = true; + layerApplyAttributes->attribute.display.boxingWindow = false; + } + break; + case 0x0b: // Start box + if (m_levelOnePage->character(r, c) == 0x0b) { + layerApplyAttributes->applyBoxingOnly = true; + layerApplyAttributes->attribute.display.boxingWindow = true; + } + break; + case 0x0d: // Double height + layerApplyAttributes->applyTextSizeOnly = true; + layerApplyAttributes->attribute.display.doubleHeight = true; + layerApplyAttributes->attribute.display.doubleWidth = false; + break; + case 0x0e: // Double width + layerApplyAttributes->applyTextSizeOnly = true; + layerApplyAttributes->attribute.display.doubleHeight = false; + layerApplyAttributes->attribute.display.doubleWidth = true; + break; + case 0x0f: // Double size + layerApplyAttributes->applyTextSizeOnly = true; + layerApplyAttributes->attribute.display.doubleHeight = true; + layerApplyAttributes->attribute.display.doubleWidth = true; + break; + } + } + // Set-at + characterCode = m_levelOnePage->character(r, c); + switch (characterCode) { + case 0x09: // Steady + layerApplyAttributes->applyFlash = true; + layerApplyAttributes->attribute.flash.mode = 0; + layerApplyAttributes->attribute.flash.ratePhase = 0; + break; + case 0x0c: // Normal size + layerApplyAttributes->applyTextSizeOnly = true; + layerApplyAttributes->attribute.display.doubleHeight = false; + layerApplyAttributes->attribute.display.doubleWidth = false; + break; + case 0x18: // Conceal + layerApplyAttributes->applyConcealOnly = true; + layerApplyAttributes->attribute.display.conceal = true; + break; + case 0x19: // Contiguous mosaics + layerApplyAttributes->applyContiguousOnly = true; + break; + case 0x1c: // Black background + layerApplyAttributes->applyBackColour = true; + layerApplyAttributes->attribute.backColour = 0x20; + break; + case 0x1d: // New background + layerApplyAttributes->applyBackColour = true; + layerApplyAttributes->attribute.backColour = m_attributeCache[c].backColour; + break; + } +} + + +ActivePosition::ActivePosition() +{ + m_row = m_column = -1; +} + +bool ActivePosition::setRow(int newRow) +{ + if (newRow < m_row) + return false; + if (newRow > m_row) { + m_row = newRow; + m_column = -1; + } + return true; +} + +bool ActivePosition::setColumn(int newColumn) +{ + if (newColumn < m_column) + return false; + if (m_row == -1) + m_row = 0; + m_column = newColumn; + return true; +} +/* +bool ActivePosition::setRowAndColumn(int newRow, int newColumn) +{ + if (!setRow(newRow)) + return false; + return setColumn(newColumn); +} +*/ diff --git a/decode.h b/decode.h new file mode 100644 index 0000000..3cd3b93 --- /dev/null +++ b/decode.h @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2020-2022 Gavin MacGregor + * + * This file is part of QTeletextMaker. + * + * QTeletextMaker 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. + * + * QTeletextMaker 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 QTeletextMaker. If not, see . + */ + +#ifndef DECODE_H +#define DECODE_H + +#include +#include +#include +#include + +#include "levelonepage.h" + +struct textCharacter { + unsigned char code=0x20; + int set=0; + int diacritical=0; +}; + +inline bool operator!=(const textCharacter &lhs, const textCharacter &rhs) +{ + return lhs.code != rhs.code || + lhs.set != rhs.set || + lhs.diacritical != rhs.diacritical; +} + +struct displayAttributes { + bool doubleHeight=false; + bool doubleWidth=false; + bool boxingWindow=false; + bool conceal=false; + bool invert=false; + bool underlineSeparated=false; + bool forceContiguous=false; +}; + +inline bool operator!=(const displayAttributes &lhs, const displayAttributes &rhs) +{ + return lhs.doubleHeight != rhs.doubleHeight || + lhs.doubleWidth != rhs.doubleWidth || + lhs.boxingWindow != rhs.boxingWindow || + lhs.conceal != rhs.conceal || + lhs.invert != rhs.invert || + lhs.underlineSeparated != rhs.underlineSeparated || + lhs.forceContiguous != rhs.forceContiguous; +} + +struct textAttributes { + int foreColour=0x07; + int backColour=0x00; + struct flashFunctions { + int mode=0; + int ratePhase=0; + int phaseNumber=0; + } flash; + displayAttributes display; + /* font style */ +}; + +inline bool operator!=(const textAttributes &lhs, const textAttributes &rhs) +{ + return lhs.foreColour != rhs.foreColour || + lhs.backColour != rhs.backColour || + lhs.flash.mode != rhs.flash.mode || + lhs.flash.ratePhase != rhs.flash.ratePhase || + lhs.flash.phaseNumber != rhs.flash.phaseNumber || + lhs.display != rhs.display; +} + +struct textCell { + textCharacter character; + textAttributes attribute; + bool bottomHalf=false; + bool rightHalf=false; + bool level1Mosaic=false; + int level1CharSet=0; +}; + +inline bool operator!=(const textCell &lhs, const textCell &rhs) +{ + return lhs.character != rhs.character || + lhs.attribute != rhs.attribute || + lhs.bottomHalf != rhs.bottomHalf || + lhs.rightHalf != rhs.rightHalf || + lhs.level1Mosaic != rhs.level1Mosaic || + lhs.level1CharSet != rhs.level1CharSet; +} + +struct applyAttributes { + bool applyForeColour=false; + bool applyBackColour=false; + bool applyFlash=false; + bool applyDisplayAttributes=false; + bool applyTextSizeOnly=false; + bool applyBoxingOnly=false; + bool applyConcealOnly=false; + bool applyContiguousOnly=false; + bool copyAboveAttributes=false; + textAttributes attribute; +}; + +class ActivePosition +{ +public: + ActivePosition(); + int row() const { return (m_row == -1) ? 0 : m_row; } + int column() const { return (m_column == -1) ? 0 : m_column; } + bool isDeployed() const { return m_row != -1; } + bool setRow(int); + bool setColumn(int); +// bool setRowAndColumn(int, int); + +private: + int m_row, m_column; +}; + + +class TextLayer +{ +public: +// TextLayer(TeletextPage* thePage) : currentPage(thePage) { }; + virtual ~TextLayer() = default; + void setTeletextPage(LevelOnePage *); + virtual textCharacter character(int, int) =0; + virtual void attributes(int, int, applyAttributes *) =0; + virtual int fullScreenColour() const =0; + virtual int fullRowColour(int) const =0; + virtual bool fullRowDownwards(int) const =0; + virtual int objectType() const =0; + virtual int originR() const { return 0; }; + virtual int originC() const { return 0; }; + void setFullScreenColour(int); + void setFullRowColour(int, int, bool); + + // Key QPair is row and column, value QPair is triplet mode and data + QMultiMap, QPair> enhanceMap; + +protected: + LevelOnePage* m_levelOnePage; + int m_layerFullScreenColour=-1; + int m_layerFullRowColour[25]; + bool m_layerFullRowDownwards[25]; + applyAttributes m_applyAttributes; +}; + +class EnhanceLayer: public TextLayer +{ +public: + EnhanceLayer(); + textCharacter character(int, int); + void attributes(int, int, applyAttributes *); + int fullScreenColour() const { return m_layerFullScreenColour; }; + int fullRowColour(int r) const { return m_layerFullRowColour[r]; }; + bool fullRowDownwards(int r) const { return m_layerFullRowDownwards[r]; }; + int objectType() const { return m_objectType; }; + int originR() const { return m_originR; }; + int originC() const { return m_originC; }; + void setObjectType(int); + void setOrigin(int, int); + +protected: + int m_objectType=0; + int m_originR=0; + int m_originC=0; + int m_rowCached=-1; + int m_rightMostColumn[25]; +}; + +class Level1Layer: public TextLayer +{ +public: + enum RowHeight { Normal=-1, TopHalf, BottomHalf }; + +// Level1Layer(TeletextPage *thePage) : TextLayer(thePage) { }; + Level1Layer(); + textCharacter character(int, int); + void attributes(int, int, applyAttributes *); + int fullScreenColour() const { return -1; }; + int fullRowColour(int) const { return -1; }; + bool fullRowDownwards(int) const { return false; }; + int objectType() const { return 0; } + RowHeight rowHeight(int r) const { return m_rowHeight[r]; }; + +private: + void updateRowCache(int); + + struct level1CacheAttributes { + int foreColour=0x07; + int backColour=0x00; + unsigned char sizeCode=0x0c; + bool mosaics=false; + bool separated=false; + bool held=false; + bool escSwitch=false; + unsigned char holdChar=0x20; + bool holdSeparated=false; + }; + level1CacheAttributes m_attributeCache[40]; + int m_rowCached=-1; + bool m_rowHasDoubleHeightAttr[25]; + RowHeight m_rowHeight[25]; +}; + +class TeletextPageDecode : public QObject +{ + Q_OBJECT + +public: + enum CharacterFragment { NormalSize, DoubleHeightTopHalf, DoubleHeightBottomHalf, DoubleWidthLeftHalf, DoubleWidthRightHalf, DoubleSizeTopLeftQuarter, DoubleSizeTopRightQuarter, DoubleSizeBottomLeftQuarter, DoubleSizeBottomRightQuarter }; + + TeletextPageDecode(); + ~TeletextPageDecode(); + bool refresh(int r, int c) const { return m_refresh[r][c]; } + void setRefresh(int, int, bool); + void decodePage(); + void decodeRow(int r); + LevelOnePage *teletextPage() const { return m_levelOnePage; }; + void setTeletextPage(LevelOnePage *); + void updateSidePanels(); + void buildEnhanceMap(TextLayer *, int=0); + + unsigned char cellCharacterCode(int r, int c) { return cellAtCharacterOrigin(r, c).character.code; }; + int cellCharacterSet(int r, int c) { return cellAtCharacterOrigin(r, c).character.set; }; + int cellCharacterDiacritical(int r, int c) { return cellAtCharacterOrigin(r, c).character.diacritical; }; + int cellForegroundCLUT(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.foreColour; }; + int cellBackgroundCLUT(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.backColour; }; + QColor cellForegroundQColor(int, int); + QColor cellBackgroundQColor(int, int); + QColor cellFlashForegroundQColor(int, int); + int cellFlashMode(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.mode; }; + int cellFlashRatePhase(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.ratePhase; }; + int cellFlash2HzPhaseNumber(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.phaseNumber; }; + CharacterFragment cellCharacterFragment(int, int) const; + bool cellBoxed(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.boxingWindow; }; + bool cellConceal(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.conceal; }; + bool cellUnderlined(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.underlineSeparated && cellAtCharacterOrigin(r, c).character.set < 24; }; + + bool level1MosaicAttribute(int r, int c) const { return m_cell[r][c].level1Mosaic; }; + int level1CharSet(int r, int c) const { return m_cell[r][c].level1CharSet; }; + + QColor fullScreenQColor() const { return m_finalFullScreenQColor; }; + QColor fullRowQColor(int r) const { return m_fullRowQColor[r]; }; + int leftSidePanelColumns() const { return m_leftSidePanelColumns; }; + int rightSidePanelColumns() const { return m_rightSidePanelColumns; }; + +public slots: + void setRenderLevel(int); + +signals: + void fullScreenColourChanged(QColor); + void fullRowColourChanged(int, QColor); + void sidePanelsChanged(); + +protected: + inline void setFullScreenColour(int); + inline void setFullRowColour(int, int); + textCell& cellAtCharacterOrigin(int, int); + + int m_finalFullScreenColour, m_renderLevel; + QColor m_finalFullScreenQColor; + int m_leftSidePanelColumns, m_rightSidePanelColumns; + Level1Layer m_level1Layer; + std::vector m_textLayer; + const int m_foregroundRemap[8] = { 0, 0, 0, 8, 8, 16, 16, 16 }; + const int m_backgroundRemap[8] = { 0, 8, 16, 8, 16, 8, 16, 24 }; + +private: + enum ColourPart { Foreground, Background, FlashForeground }; + + QColor cellQColor(int, int, ColourPart); + + textCell m_cell[25][72]; + bool m_refresh[25][72]; + LevelOnePage* m_levelOnePage; + int m_fullRowColour[25]; + QColor m_fullRowQColor[25]; +}; + +static const QMap g0CharacterMap { + { 0x00, 12 }, { 0x01, 15 }, { 0x02, 22 }, { 0x03, 16 }, { 0x04, 14 }, { 0x05, 19 }, { 0x06, 11 }, + { 0x08, 18 }, { 0x09, 15 }, { 0x0a, 22 }, { 0x0b, 16 }, { 0x0c, 14 }, { 0x0e, 11 }, + { 0x10, 12 }, { 0x11, 15 }, { 0x12, 22 }, { 0x13, 16 }, { 0x14, 14 }, { 0x15, 19 }, { 0x16, 23 }, + { 0x1d, 21 }, { 0x1f, 20 }, + { 0x20, 1 }, { 0x21, 15 }, { 0x22, 13 }, { 0x23, 17 }, { 0x24, 2 }, { 0x25, 3 }, { 0x26, 11 }, + { 0x36, 23 }, { 0x37, 4 }, + { 0x40, 12 }, { 0x44, 14 }, { 0x47, 5 }, + { 0x55, 6 }, { 0x57, 5 } +}; + +#endif diff --git a/mainwidget.cpp b/mainwidget.cpp index 98b0247..79f98fd 100644 --- a/mainwidget.cpp +++ b/mainwidget.cpp @@ -38,6 +38,7 @@ #include "mainwidget.h" +#include "decode.h" #include "document.h" #include "keymap.h" #include "levelonecommands.h" @@ -52,16 +53,18 @@ TeletextWidget::TeletextWidget(QFrame *parent) : QFrame(parent) this->setAttribute(Qt::WA_InputMethodEnabled, true); m_teletextDocument = new TeletextDocument(); m_levelOnePage = m_teletextDocument->currentSubPage(); - m_pageRender.setTeletextPage(m_levelOnePage); + m_pageDecode.setTeletextPage(m_levelOnePage); + m_pageRender.setDecoder(&m_pageDecode); m_insertMode = false; m_selectionInProgress = false; setFocusPolicy(Qt::StrongFocus); m_flashTiming = m_flashPhase = 0; connect(&m_pageRender, &TeletextPageRender::flashChanged, this, &TeletextWidget::updateFlashTimer); - connect(&m_pageRender, &TeletextPageRender::sidePanelsChanged, this, &TeletextWidget::changeSize); + connect(&m_pageDecode, &TeletextPageDecode::sidePanelsChanged, this, &TeletextWidget::changeSize); connect(m_teletextDocument, &TeletextDocument::subPageSelected, this, &TeletextWidget::subPageSelected); connect(m_teletextDocument, &TeletextDocument::contentsChange, this, &TeletextWidget::refreshRow); connect(m_teletextDocument, &TeletextDocument::refreshNeeded, this, &TeletextWidget::refreshPage); + connect(m_teletextDocument, &TeletextDocument::colourChanged, &m_pageRender, &TeletextPageRender::colourChanged); } TeletextWidget::~TeletextWidget() @@ -85,20 +88,21 @@ void TeletextWidget::inputMethodEvent(QInputMethodEvent* event) void TeletextWidget::subPageSelected() { m_levelOnePage = m_teletextDocument->currentSubPage(); - m_pageRender.setTeletextPage(m_levelOnePage); - refreshPage(); + m_pageDecode.setTeletextPage(m_levelOnePage); + m_pageDecode.decodePage(); + m_pageRender.renderPage(true); + update(); } void TeletextWidget::refreshRow(int rowChanged) { - m_pageRender.renderPage(rowChanged); + m_pageDecode.decodeRow(rowChanged); update(); } void TeletextWidget::refreshPage() { - m_pageRender.decodePage(); - m_pageRender.renderPage(); + m_pageDecode.decodePage(); update(); } @@ -107,11 +111,12 @@ void TeletextWidget::paintEvent(QPaintEvent *event) Q_UNUSED(event); QPainter widgetPainter(this); - widgetPainter.drawPixmap(m_pageRender.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 0, 0, 480, 250); - if (m_pageRender.leftSidePanelColumns()) - widgetPainter.drawPixmap(0, 0, *m_pageRender.pagePixmap(m_flashPhase), 864-m_pageRender.leftSidePanelColumns()*12, 0, m_pageRender.leftSidePanelColumns()*12, 250); - if (m_pageRender.rightSidePanelColumns()) - widgetPainter.drawPixmap(480+m_pageRender.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 480, 0, m_pageRender.rightSidePanelColumns()*12, 250); + m_pageRender.renderPage(); + widgetPainter.drawPixmap(m_pageDecode.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 0, 0, 480, 250); + if (m_pageDecode.leftSidePanelColumns()) + widgetPainter.drawPixmap(0, 0, *m_pageRender.pagePixmap(m_flashPhase), 864-m_pageDecode.leftSidePanelColumns()*12, 0, m_pageDecode.leftSidePanelColumns()*12, 250); + if (m_pageDecode.rightSidePanelColumns()) + widgetPainter.drawPixmap(480+m_pageDecode.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 480, 0, m_pageDecode.rightSidePanelColumns()*12, 250); } void TeletextWidget::updateFlashTimer(int newFlashTimer) @@ -155,25 +160,29 @@ void TeletextWidget::setInsertMode(bool insertMode) m_insertMode = insertMode; } -void TeletextWidget::toggleReveal(bool revealOn) +void TeletextWidget::setReveal(bool reveal) { - m_pageRender.setReveal(revealOn); + m_pageRender.setReveal(reveal); update(); } -void TeletextWidget::toggleMix(bool mixOn) +void TeletextWidget::setMix(bool mix) { - m_pageRender.setMix(mixOn); + m_pageRender.setMix(mix); + update(); +} + +void TeletextWidget::setShowControlCodes(bool showControlCodes) +{ + m_pageRender.setShowControlCodes(showControlCodes); update(); } void TeletextWidget::setControlBit(int bitNumber, bool active) { m_levelOnePage->setControlBit(bitNumber, active); - if (bitNumber == 1 || bitNumber == 2) { - m_pageRender.decodePage(); - m_pageRender.renderPage(); - } + if (bitNumber == 1 || bitNumber == 2) + m_pageDecode.decodePage(); } void TeletextWidget::setDefaultCharSet(int newDefaultCharSet) @@ -189,31 +198,27 @@ void TeletextWidget::setDefaultNOS(int newDefaultNOS) void TeletextWidget::setDefaultScreenColour(int newColour) { m_levelOnePage->setDefaultScreenColour(newColour); - m_pageRender.decodePage(); - m_pageRender.renderPage(); + m_pageDecode.decodePage(); } void TeletextWidget::setDefaultRowColour(int newColour) { m_levelOnePage->setDefaultRowColour(newColour); - m_pageRender.decodePage(); - m_pageRender.renderPage(); + m_pageDecode.decodePage(); update(); } void TeletextWidget::setColourTableRemap(int newMap) { m_levelOnePage->setColourTableRemap(newMap); - m_pageRender.decodePage(); - m_pageRender.renderPage(); + m_pageDecode.decodePage(); update(); } void TeletextWidget::setBlackBackgroundSubst(bool substOn) { m_levelOnePage->setBlackBackgroundSubst(substOn); - m_pageRender.decodePage(); - m_pageRender.renderPage(); + m_pageDecode.decodePage(); update(); } @@ -225,18 +230,18 @@ void TeletextWidget::setSidePanelWidths(int newLeftSidePanelColumns, int newRigh m_levelOnePage->setSidePanelColumns((newLeftSidePanelColumns == 16) ? 0 : newLeftSidePanelColumns); else m_levelOnePage->setSidePanelColumns((newRightSidePanelColumns == 0) ? 0 : 16-newRightSidePanelColumns); - m_pageRender.updateSidePanels(); + m_pageDecode.updateSidePanels(); } void TeletextWidget::setSidePanelAtL35Only(bool newSidePanelAtL35Only) { m_levelOnePage->setSidePanelStatusL25(!newSidePanelAtL35Only); - m_pageRender.updateSidePanels(); + m_pageDecode.updateSidePanels(); } void TeletextWidget::changeSize() { - setFixedSize(QSize(480+(pageRender()->leftSidePanelColumns()+pageRender()->rightSidePanelColumns())*12, 250)); + setFixedSize(QSize(480+(pageDecode()->leftSidePanelColumns()+pageDecode()->rightSidePanelColumns())*12, 250)); emit sizeChanged(); } @@ -245,14 +250,14 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event) if (event->key() < 0x01000000) { // A character-typing key was pressed // Try to keymap it, if not keymapped then plain ASCII code (may be) returned - char mappedKeyPress = keymapping[m_pageRender.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn())].value(event->text().at(0), *qPrintable(event->text())); + char mappedKeyPress = keymapping[m_pageDecode.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn())].value(event->text().at(0), *qPrintable(event->text())); if (mappedKeyPress < 0x20) return; // If outside ASCII map then the character can't be represented by current Level 1 character set // Map it to block character so it doesn't need to be inserted-between later on if (mappedKeyPress & 0x80) mappedKeyPress = 0x7f; - if (m_pageRender.level1MosaicAttribute(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()) && (mappedKeyPress < 0x40 || mappedKeyPress > 0x5f)) { + if (m_pageDecode.level1MosaicAttribute(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()) && (mappedKeyPress < 0x40 || mappedKeyPress > 0x5f)) { // We're on a mosaic and a blast-through character was NOT pressed if (event->key() >= Qt::Key_1 && event->key() <= Qt::Key_9 && event->modifiers() & Qt::KeypadModifier) { switch (event->key()) { @@ -382,8 +387,8 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event) m_teletextDocument->selectSubPagePrevious(); break; case Qt::Key_F5: - m_pageRender.decodePage(); - m_pageRender.renderPage(); + m_pageDecode.decodePage(); + m_pageRender.renderPage(true); update(); break; default: @@ -420,7 +425,7 @@ void TeletextWidget::selectionToClipboard() nativeData[i++] = m_teletextDocument->currentSubPage()->character(r, c); if (m_teletextDocument->currentSubPage()->character(r, c) >= 0x20) - plainTextData.append(keymapping[m_pageRender.level1CharSet(r, c)].key(m_teletextDocument->currentSubPage()->character(r, c), m_teletextDocument->currentSubPage()->character(r, c))); + plainTextData.append(keymapping[m_pageDecode.level1CharSet(r, c)].key(m_teletextDocument->currentSubPage()->character(r, c), m_teletextDocument->currentSubPage()->character(r, c))); else plainTextData.append(' '); } @@ -453,13 +458,13 @@ void TeletextWidget::copy() void TeletextWidget::paste() { - m_teletextDocument->undoStack()->push(new PasteCommand(m_teletextDocument, m_pageRender.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()))); + m_teletextDocument->undoStack()->push(new PasteCommand(m_teletextDocument, m_pageDecode.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()))); } QPair TeletextWidget::mouseToRowAndColumn(const QPoint &mousePosition) { int row = mousePosition.y() / 10; - int column = mousePosition.x() / 12 - m_pageRender.leftSidePanelColumns(); + int column = mousePosition.x() / 12 - m_pageDecode.leftSidePanelColumns(); if (row < 1) row = 1; if (row > 24) @@ -655,6 +660,22 @@ void LevelOneScene::updateSelection() m_selectionRectItem->setVisible(true); } +void LevelOneScene::setMix(bool mix) +{ + if (mix) { + m_fullScreenTopRectItem->setBrush(Qt::transparent); + m_fullScreenBottomRectItem->setBrush(Qt::transparent); + for (int r=0; r<25; r++) { + m_fullRowLeftRectItem[r]->setBrush(Qt::transparent); + m_fullRowRightRectItem[r]->setBrush(Qt::transparent); + } + } else { + setFullScreenColour(static_cast(m_levelOneProxyWidget->widget())->pageDecode()->fullScreenQColor()); + for (int r=0; r<25; r++) + setFullRowColour(r, static_cast(m_levelOneProxyWidget->widget())->pageDecode()->fullRowQColor(r)); + } +} + void LevelOneScene::toggleGrid(bool gridOn) { m_grid = gridOn; @@ -700,12 +721,16 @@ bool LevelOneScene::eventFilter(QObject *object, QEvent *event) void LevelOneScene::setFullScreenColour(const QColor &newColor) { - m_fullScreenTopRectItem->setBrush(QBrush(newColor)); - m_fullScreenBottomRectItem->setBrush(QBrush(newColor)); + if (!static_cast(m_levelOneProxyWidget->widget())->pageRender()->mix()) { + m_fullScreenTopRectItem->setBrush(newColor); + m_fullScreenBottomRectItem->setBrush(newColor); + } } void LevelOneScene::setFullRowColour(int row, const QColor &newColor) { - m_fullRowLeftRectItem[row]->setBrush(QBrush(newColor)); - m_fullRowRightRectItem[row]->setBrush(QBrush(newColor)); + if (!static_cast(m_levelOneProxyWidget->widget())->pageRender()->mix()) { + m_fullRowLeftRectItem[row]->setBrush(newColor); + m_fullRowRightRectItem[row]->setBrush(newColor); + } } diff --git a/mainwidget.h b/mainwidget.h index 50a94f0..27be91f 100644 --- a/mainwidget.h +++ b/mainwidget.h @@ -29,6 +29,7 @@ #include #include +#include "decode.h" #include "document.h" #include "levelonepage.h" #include "render.h" @@ -46,12 +47,14 @@ public: void toggleCharacterBit(unsigned char); bool insertMode() const { return m_insertMode; }; void setInsertMode(bool); + bool showControlCodes() const { return m_pageRender.showControlCodes(); }; - QSize sizeHint() { return QSize(480+(pageRender()->leftSidePanelColumns()+pageRender()->rightSidePanelColumns())*12, 250); } + QSize sizeHint() { return QSize(480+(pageDecode()->leftSidePanelColumns()+pageDecode()->rightSidePanelColumns())*12, 250); } void inputMethodEvent(QInputMethodEvent *); TeletextDocument* document() const { return m_teletextDocument; } + TeletextPageDecode *pageDecode() { return &m_pageDecode; } TeletextPageRender *pageRender() { return &m_pageRender; } signals: @@ -61,8 +64,9 @@ signals: public slots: void subPageSelected(); void refreshPage(); - void toggleReveal(bool); - void toggleMix(bool); + void setReveal(bool); + void setMix(bool); + void setShowControlCodes(bool); void updateFlashTimer(int); void pauseFlash(bool); void refreshRow(int); @@ -92,6 +96,7 @@ protected: void focusInEvent(QFocusEvent *event) override; void focusOutEvent(QFocusEvent *event) override; + TeletextPageDecode m_pageDecode; TeletextPageRender m_pageRender; private: @@ -119,6 +124,7 @@ public: public slots: void updateCursor(); void updateSelection(); + void setMix(bool); void toggleGrid(bool); void hideGUIElements(bool); void setFullScreenColour(const QColor &); diff --git a/mainwindow.cpp b/mainwindow.cpp index d6636dd..d00f476 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -157,28 +157,32 @@ void MainWindow::exportPNG() // Prepare widget image for extraction m_textWidget->pauseFlash(true); m_textScene->hideGUIElements(true); - bool reshowCodes = m_textWidget->pageRender()->showCodes(); - if (reshowCodes) - m_textWidget->pageRender()->setShowCodes(false); + bool reshowControlCodes = m_textWidget->showControlCodes(); + if (reshowControlCodes) + m_textWidget->setShowControlCodes(false); // Disable exporting in Mix mode as it corrupts the background bool reMix = m_textWidget->pageRender()->mix(); - if (reMix) - m_textWidget->pageRender()->setMix(false); + if (reMix) { + m_textWidget->setMix(false); + m_textScene->setMix(false); + } // Extract the image from the scene QImage interImage = QImage(m_textScene->sceneRect().size().toSize(), QImage::Format_RGB32); // This ought to make the background transparent in Mix mode, but it doesn't -// if (m_textWidget->pageRender()->mix()) +// if (m_textWidget->pageDecode()->mix()) // interImage.fill(QColor(0, 0, 0, 0)); QPainter interPainter(&interImage); m_textScene->render(&interPainter); // Now we've extracted the image we can put the GUI things back m_textScene->hideGUIElements(false); - if (reshowCodes) - m_textWidget->pageRender()->setShowCodes(true); - if (reMix) - m_textWidget->pageRender()->setMix(true); + if (reshowControlCodes) + m_textWidget->setShowControlCodes(true); + if (reMix) { + m_textWidget->setMix(true); + m_textScene->setMix(true); + } m_textWidget->pauseFlash(false); // Now scale the extracted image to the selected aspect ratio @@ -256,8 +260,8 @@ void MainWindow::init() connect(m_textWidget->document(), &TeletextDocument::aboutToChangeSubPage, m_x26DockWidget, &X26DockWidget::unloadX26List); connect(m_textWidget->document(), &TeletextDocument::subPageSelected, this, &MainWindow::updatePageWidgets); connect(m_textWidget, &TeletextWidget::sizeChanged, this, &MainWindow::setSceneDimensions); - connect(m_textWidget->pageRender(), &TeletextPageRender::fullScreenColourChanged, m_textScene, &LevelOneScene::setFullScreenColour); - connect(m_textWidget->pageRender(), &TeletextPageRender::fullRowColourChanged, m_textScene, &LevelOneScene::setFullRowColour); + connect(m_textWidget->pageDecode(), &TeletextPageDecode::fullScreenColourChanged, m_textScene, &LevelOneScene::setFullScreenColour); + connect(m_textWidget->pageDecode(), &TeletextPageDecode::fullRowColourChanged, m_textScene, &LevelOneScene::setFullRowColour); connect(m_textWidget, &TeletextWidget::insertKeyPressed, this, &MainWindow::toggleInsertMode); connect(m_textScene, &LevelOneScene::mouseZoomIn, this, &MainWindow::zoomIn); @@ -456,13 +460,14 @@ void MainWindow::createActions() revealAct->setCheckable(true); revealAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); revealAct->setStatusTip(tr("Toggle reveal")); - connect(revealAct, &QAction::toggled, m_textWidget, &TeletextWidget::toggleReveal); + connect(revealAct, &QAction::toggled, m_textWidget, &TeletextWidget::setReveal); QAction *mixAct = viewMenu->addAction(tr("&Mix")); mixAct->setCheckable(true); mixAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); mixAct->setStatusTip(tr("Toggle mix")); - connect(mixAct, &QAction::toggled, m_textWidget, &TeletextWidget::toggleMix); + connect(mixAct, &QAction::toggled, m_textWidget, &TeletextWidget::setMix); + connect(mixAct, &QAction::toggled, m_textScene, &LevelOneScene::setMix); QAction *gridAct = viewMenu->addAction(tr("&Grid")); gridAct->setCheckable(true); @@ -470,11 +475,11 @@ void MainWindow::createActions() gridAct->setStatusTip(tr("Toggle the text grid")); connect(gridAct, &QAction::toggled, m_textScene, &LevelOneScene::toggleGrid); - QAction *showCodesAct = viewMenu->addAction(tr("Show codes")); - showCodesAct->setCheckable(true); - showCodesAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T)); - showCodesAct->setStatusTip(tr("Toggle showing of control codes")); - connect(showCodesAct, &QAction::toggled, m_textWidget->pageRender(), &TeletextPageRender::setShowCodes); + QAction *showControlCodesAct = viewMenu->addAction(tr("Show control codes")); + showControlCodesAct->setCheckable(true); + showControlCodesAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T)); + showControlCodesAct->setStatusTip(tr("Toggle showing of control codes")); + connect(showControlCodesAct, &QAction::toggled, m_textWidget, &TeletextWidget::setShowControlCodes); viewMenu->addSeparator(); @@ -668,7 +673,7 @@ void MainWindow::setSceneDimensions() else newSceneWidth = m_textWidget->width() + leftRightBorders[m_viewBorder]*2; - m_textScene->setBorderDimensions(newSceneWidth, 250+topBottomBorders[m_viewBorder]*2, m_textWidget->width(), m_textWidget->pageRender()->leftSidePanelColumns(), m_textWidget->pageRender()->rightSidePanelColumns()); + m_textScene->setBorderDimensions(newSceneWidth, 250+topBottomBorders[m_viewBorder]*2, m_textWidget->width(), m_textWidget->pageDecode()->leftSidePanelColumns(), m_textWidget->pageDecode()->rightSidePanelColumns()); m_textView->setTransform(QTransform((1+(float)m_viewZoom/2)*aspectRatioHorizontalScaling[m_viewAspectRatio], 0, 0, 1+(float)m_viewZoom/2, 0, 0)); } @@ -792,10 +797,10 @@ void MainWindow::createStatusBar() statusBar()->addPermanentWidget(m_levelRadioButton[i]); } m_levelRadioButton[0]->toggle(); - connect(m_levelRadioButton[0], &QAbstractButton::clicked, [=]() { m_textWidget->pageRender()->setRenderLevel(0); m_textWidget->update(); }); - connect(m_levelRadioButton[1], &QAbstractButton::clicked, [=]() { m_textWidget->pageRender()->setRenderLevel(1); m_textWidget->update(); }); - connect(m_levelRadioButton[2], &QAbstractButton::clicked, [=]() { m_textWidget->pageRender()->setRenderLevel(2); m_textWidget->update(); }); - connect(m_levelRadioButton[3], &QAbstractButton::clicked, [=]() { m_textWidget->pageRender()->setRenderLevel(3); m_textWidget->update(); }); + connect(m_levelRadioButton[0], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setRenderLevel(0); m_textWidget->update(); }); + connect(m_levelRadioButton[1], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setRenderLevel(1); m_textWidget->update(); }); + connect(m_levelRadioButton[2], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setRenderLevel(2); m_textWidget->update(); }); + connect(m_levelRadioButton[3], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setRenderLevel(3); m_textWidget->update(); }); statusBar()->showMessage(tr("Ready")); } @@ -902,7 +907,7 @@ void MainWindow::loadFile(const QString &fileName) levelSeen = m_textWidget->document()->levelRequired(); m_levelRadioButton[levelSeen]->toggle(); - m_textWidget->pageRender()->setRenderLevel(levelSeen); + m_textWidget->pageDecode()->setRenderLevel(levelSeen); updatePageWidgets(); QApplication::restoreOverrideCursor(); diff --git a/qteletextmaker.pro b/qteletextmaker.pro index 3aac32a..296eef4 100644 --- a/qteletextmaker.pro +++ b/qteletextmaker.pro @@ -1,7 +1,8 @@ QT += widgets requires(qtConfig(filedialog)) -HEADERS = document.h \ +HEADERS = decode.h \ + document.h \ hamming.h \ keymap.h \ levelonecommands.h \ @@ -19,7 +20,8 @@ HEADERS = document.h \ x26dockwidget.h \ x26model.h \ x26triplets.h -SOURCES = document.cpp \ +SOURCES = decode.cpp \ + document.cpp \ levelonecommands.cpp \ levelonepage.cpp \ loadsave.cpp \ diff --git a/render.cpp b/render.cpp index f451e7f..0442cd0 100644 --- a/render.cpp +++ b/render.cpp @@ -17,15 +17,14 @@ * along with QTeletextMaker. If not, see . */ -#include +#include #include -//#include -#include -#include -#include +#include #include "render.h" +#include "decode.h" + int TeletextFontBitmap::s_instances = 0; QBitmap *TeletextFontBitmap::s_fontBitmap = nullptr; @@ -44,1046 +43,411 @@ TeletextFontBitmap::~TeletextFontBitmap() delete s_fontBitmap; } + TeletextPageRender::TeletextPageRender() { for (int i=0; i<6; i++) m_pagePixmap[i] = new QPixmap(864, 250); m_pagePixmap[0]->fill(Qt::transparent); - m_mix = m_reveal = m_showCodes = false; - m_renderLevel = 0; - m_flashRequired = 0; - m_finalFullScreenColour = 0; - m_finalFullScreenQColor.setRgb(0, 0, 0); - for (int r=0; r<25; r++) { - m_flashRow[r] = 0; - m_fullRowColour[r] = 0; - m_fullRowQColor[r].setRgb(0, 0, 0); - m_concealRow[r] = false; - } - m_leftSidePanelColumns = m_rightSidePanelColumns = 0; - m_textLayer.push_back(&m_level1Layer); - m_textLayer.push_back(new EnhanceLayer); + + m_reveal = false; + m_mix = false; + m_showControlCodes = false; + m_flashBuffersHz = 0; + + for (int r=0; r<25; r++) + for (int c=0; c<40; c++) + m_controlCodeCache[r][c] = 0x7f; } TeletextPageRender::~TeletextPageRender() { - while (m_textLayer.size()>1) { - delete m_textLayer.back(); - m_textLayer.pop_back(); - } for (int i=0; i<6; i++) delete m_pagePixmap[i]; } -void TeletextPageRender::setTeletextPage(LevelOnePage *newCurrentPage) +void TeletextPageRender::setDecoder(TeletextPageDecode *decoder) { - m_levelOnePage = newCurrentPage; - m_level1Layer.setTeletextPage(newCurrentPage); - updateSidePanels(); + m_decoder = decoder; } -void TeletextPageRender::setReveal(bool newReveal) +inline void TeletextPageRender::drawFromFontBitmap(QPainter &pixmapPainter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment) { - m_reveal = newReveal; - for (int r=0; r<25; r++) - if (m_concealRow[r]) - renderPage(r); -} - -void TeletextPageRender::setMix(bool newMix) -{ - m_mix = newMix; - decodePage(); - renderPage(); -} - -void TeletextPageRender::setRenderLevel(int newRenderLevel) -{ - if (newRenderLevel == m_renderLevel) - return; - m_renderLevel = newRenderLevel; - decodePage(); - renderPage(); -} - -void TeletextPageRender::setShowCodes(bool newShowCodes) -{ - m_showCodes = newShowCodes; - renderPage(); -} - -void TeletextPageRender::updateSidePanels() -{ - int oldLeftSidePanelColumns = m_leftSidePanelColumns; - int oldRightSidePanelColumns = m_rightSidePanelColumns; - - if (m_renderLevel >= (3-m_levelOnePage->sidePanelStatusL25()) && m_levelOnePage->leftSidePanelDisplayed()) - m_leftSidePanelColumns = (m_levelOnePage->sidePanelColumns() == 0) ? 16 : m_levelOnePage->sidePanelColumns(); - else - m_leftSidePanelColumns = 0; - - if (m_renderLevel >= (3-m_levelOnePage->sidePanelStatusL25()) && m_levelOnePage->rightSidePanelDisplayed()) - m_rightSidePanelColumns = 16-m_levelOnePage->sidePanelColumns(); - else - m_rightSidePanelColumns = 0; - - if (m_leftSidePanelColumns != oldLeftSidePanelColumns || m_rightSidePanelColumns != oldRightSidePanelColumns) { - emit sidePanelsChanged(); - renderPage(); + switch (characterFragment) { + case TeletextPageDecode::NormalSize: + pixmapPainter.drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 12, 10); + break; + case TeletextPageDecode::DoubleHeightTopHalf: + pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 12, 5); + break; + case TeletextPageDecode::DoubleHeightBottomHalf: + pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10+5, 12, 5); + break; + case TeletextPageDecode::DoubleWidthLeftHalf: + pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 6, 10); + break; + case TeletextPageDecode::DoubleWidthRightHalf: + pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12+6, characterSet*10, 6, 10); + break; + case TeletextPageDecode::DoubleSizeTopLeftQuarter: + pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 6, 5); + break; + case TeletextPageDecode::DoubleSizeTopRightQuarter: + pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12+6, characterSet*10, 6, 5); + break; + case TeletextPageDecode::DoubleSizeBottomLeftQuarter: + pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10+5, 6, 5); + break; + case TeletextPageDecode::DoubleSizeBottomRightQuarter: + pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12+6, characterSet*10+5, 6, 5); + break; } } -void TeletextPageRender::buildEnhanceMap(TextLayer *enhanceLayer, int tripletNumber) +inline void TeletextPageRender::drawCharacter(QPainter &pixmapPainter, int r, int c, unsigned char characterCode, int characterSet, int characterDiacritical, TeletextPageDecode::CharacterFragment characterFragment) { - bool terminatorFound=false; - ActivePosition activePosition; - const X26Triplet *x26Triplet; - int originModifierR=0; - int originModifierC=0; + const bool dontUnderline = characterCode == 0x00; + if (dontUnderline) + characterCode = 0x20; - do { - x26Triplet = &m_levelOnePage->enhancements()->at(tripletNumber); - if (x26Triplet->isRowTriplet()) - // Row address group - switch (x26Triplet->mode()) { - case 0x00: // Full screen colour - if (m_renderLevel >= 2 && ((x26Triplet->data() & 0x60) == 0x00) && !activePosition.isDeployed()) - enhanceLayer->setFullScreenColour(x26Triplet->data()); - break; - case 0x01: // Full row colour - if (m_renderLevel >= 2 && activePosition.setRow(x26Triplet->addressRow()) && ((x26Triplet->data() & 0x60) == 0x00 || (x26Triplet->data() & 0x60) == 0x60)) - enhanceLayer->setFullRowColour(activePosition.row(), x26Triplet->data() & 0x1f, (x26Triplet->data() & 0x60) == 0x60); - break; - case 0x04: // Set active position - if (activePosition.setRow(x26Triplet->addressRow()) && m_renderLevel >= 2 && x26Triplet->data() < 40) - activePosition.setColumn(x26Triplet->data()); - break; - case 0x07: // Address row 0 - if (x26Triplet->address() == 0x3f && !activePosition.isDeployed()) { - activePosition.setRow(0); - activePosition.setColumn(8); - if (m_renderLevel >= 2 && ((x26Triplet->data() & 0x60) == 0x00 || (x26Triplet->data() & 0x60) == 0x60)) - enhanceLayer->setFullRowColour(0, x26Triplet->data() & 0x1f, (x26Triplet->data() & 0x60) == 0x60); - } - break; - case 0x10: // Origin modifier - if (m_renderLevel >= 2 && (tripletNumber+1) < m_levelOnePage->enhancements()->size() && m_levelOnePage->enhancements()->at(tripletNumber+1).mode() >= 0x11 && m_levelOnePage->enhancements()->at(tripletNumber+1).mode() <= 0x13 && x26Triplet->address() >= 40 && x26Triplet->data() < 72) { - originModifierR = x26Triplet->address()-40; - originModifierC = x26Triplet->data(); - } - break; - case 0x11 ... 0x13: // Invoke Object - if (m_renderLevel >= 2) { - if ((x26Triplet->address() & 0x18) == 0x08) { - // Local Object - // Check if the pointer in the Invocation triplet is valid - // Can't point to triplets 13-15; only triplets 0-12 per packet - if ((x26Triplet->data() & 0x0f) > 12) - break; - int tripletPointer = ((x26Triplet->data() >> 4) | ((x26Triplet->address() & 1) << 3)) * 13 + (x26Triplet->data() & 0x0f); - // Can't point to triplet beyond the end of the Local Enhancement Data - if ((tripletPointer+1) >= m_levelOnePage->enhancements()->size()) - break; - // Check if we're pointing to an actual Object Definition of the same type - if ((x26Triplet->mode() | 0x04) != m_levelOnePage->enhancements()->at(tripletPointer).mode()) - break; - // The Object Definition can't declare it's at triplet 13-15; only triplets 0-12 per packet - if ((m_levelOnePage->enhancements()->at(tripletPointer).data() & 0x0f) > 12) - break; - // Check if the Object Definition triplet is where it declares it is - if ((((m_levelOnePage->enhancements()->at(tripletPointer).data() >> 4) | ((m_levelOnePage->enhancements()->at(tripletPointer).address() & 1) << 3)) * 13 + (m_levelOnePage->enhancements()->at(tripletPointer).data() & 0x0f)) != tripletPointer) - break; - // Check if (sub)Object type can be invoked by Object type we're within - if (enhanceLayer->objectType() >= (x26Triplet->mode() & 0x03)) - break; - // Is the object required at the current presentation Level? - if (m_renderLevel == 2 && (m_levelOnePage->enhancements()->at(tripletPointer).address() & 0x08) == 0x00) - break; - if (m_renderLevel == 3 && (m_levelOnePage->enhancements()->at(tripletPointer).address() & 0x10) == 0x00) - break; - EnhanceLayer *newLayer = new EnhanceLayer; - m_textLayer.push_back(newLayer); - newLayer->setObjectType(x26Triplet->mode() & 0x03); - newLayer->setOrigin(enhanceLayer->originR() + activePosition.row() + originModifierR, enhanceLayer->originC() + activePosition.column() + originModifierC); - buildEnhanceMap(newLayer, tripletPointer+1); - } else - qDebug("POP or GPOP"); - originModifierR = originModifierC = 0; - } - break; - case 0x15 ... 0x17: // Define Object, also used as terminator - terminatorFound = true; - break; - case 0x1f: // Terminator - if (x26Triplet->address() == 63) - terminatorFound = true; - break; - } - else { - // Column address group - bool columnTripletActioned = true; - switch (x26Triplet->mode()) { - // First we deal with column triplets that are also valid at Level 1.5 - case 0x0b: // G3 mosaic character at Level 2.5 - if (m_renderLevel <= 1) - break; - // fall-through - case 0x02: // G3 mosaic character at Level 1.5 - case 0x0f: // G2 character - case 0x10 ... 0x1f: // Diacritical mark - if (activePosition.setColumn(x26Triplet->addressColumn()) && x26Triplet->data() >= 0x20) - enhanceLayer->enhanceMap.insert(qMakePair(activePosition.row(), activePosition.column()), qMakePair(x26Triplet->mode() | 0x20, x26Triplet->data())); - break; - // Make sure that PDC and reserved triplets don't affect the Active Position - case 0x04 ... 0x06: // 0x04 and 0x05 are reserved, 0x06 for PDC - case 0x0a: // Reserved - break; - default: - columnTripletActioned = false; - } - // All remaining possible column triplets at Level 2.5 affect the Active Position - if (m_renderLevel >= 2 && !columnTripletActioned && activePosition.setColumn(x26Triplet->addressColumn())) - enhanceLayer->enhanceMap.insert(qMakePair(activePosition.row(), activePosition.column()), qMakePair(x26Triplet->mode() | 0x20, x26Triplet->data())); - } - tripletNumber++; - } while (!terminatorFound && tripletNumber < m_levelOnePage->enhancements()->size()); -} - -void TeletextPageRender::decodePage() -{ - int currentFullRowColour, downwardsFullRowColour; - int renderedFullScreenColour; - - struct { - bool operator() (TextLayer *i, TextLayer *j) { return (i->objectType() < j->objectType()); } - } compareLayer; - - updateSidePanels(); - - while (m_textLayer.size()>2) { - delete m_textLayer.back(); - m_textLayer.pop_back(); - } - - renderedFullScreenColour = (m_renderLevel >= 2) ? m_levelOnePage->defaultScreenColour() : 0; - downwardsFullRowColour = (m_renderLevel >= 2) ? m_levelOnePage->defaultRowColour() : 0; - setFullScreenColour(renderedFullScreenColour); - for (int r=0; r<25; r++) - setFullRowColour(r ,downwardsFullRowColour); - - m_textLayer[1]->enhanceMap.clear(); - if (m_renderLevel == 0 || m_levelOnePage->enhancements()->isEmpty()) - return; - - m_textLayer[1]->setFullScreenColour(-1); - for (int r=0; r<25; r++) - m_textLayer[1]->setFullRowColour(r, -1, false); - buildEnhanceMap(m_textLayer[1]); - - if (m_textLayer.size() > 2) - std::stable_sort(m_textLayer.begin()+2, m_textLayer.end(), compareLayer); - - if (m_renderLevel <= 1) - return; - - if (m_textLayer[1]->fullScreenColour() != -1) - downwardsFullRowColour = m_textLayer[1]->fullScreenColour(); - for (int r=0; r<25; r++) { - for (int l=0; l<2; l++) { - if (r == 0 && m_textLayer[l]->fullScreenColour() != - 1) - renderedFullScreenColour = m_textLayer[l]->fullScreenColour(); - if (m_textLayer[l]->fullRowColour(r) == - 1) - currentFullRowColour = downwardsFullRowColour; - else { - currentFullRowColour = m_textLayer[l]->fullRowColour(r); - if (m_textLayer[l]->fullRowDownwards(r)) - downwardsFullRowColour = currentFullRowColour; - } - } - setFullRowColour(r ,currentFullRowColour); - } - setFullScreenColour(renderedFullScreenColour); -} - -void TeletextPageRender::renderPage() -{ -// QTime renderPageTime; - -// renderPageTime.start(); - for (int r=0; r<25; r++) - renderPage(r); -// qDebug("Full page render: %d ms", renderPageTime.elapsed()); -} - -void TeletextPageRender::renderPage(int r) -{ - QPainter pixmapPainter; - QColor foreQColour, backQColour; - int c; - int phaseNumberRender = 0; - int flashRowRequired = 0; - bool renderNextRow = false; - bool applyRightHalf = false; - bool previouslyDoubleHeight, previouslyBottomHalf, underlined; - bool doubleHeightFound = false; - textCharacter resultCharacter, layerCharacter; - applyAttributes layerApplyAttributes; - textAttributes underlyingAttributes, resultAttributes; - int level1CharSet; - - auto renderCharacter=[&]() - { - if (m_cell[r][c].bottomHalf || m_cell[r][c].rightHalf) - return; - int charWidth = resultAttributes.display.doubleWidth ? 24 : 12; - int charHeight = resultAttributes.display.doubleHeight ? 20 : 10; - bool transparentBackground = (m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle) || m_mix) && !resultAttributes.display.boxingWindow; - // bool transparentForeground = false; - if (transparentBackground) { + // If either foreground or background is set to transparent + // tinker with the QPainter settings so we get the desired result + if (!pixmapPainter.background().isOpaque()) { + if (pixmapPainter.pen().color().alpha() == 0) { + // Transparent foreground and background pixmapPainter.setCompositionMode(QPainter::CompositionMode_Clear); - pixmapPainter.eraseRect(c*12, r*10, charWidth, charHeight); + pixmapPainter.eraseRect(c*12, r*10, 12, 10); pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); - } - if ((m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle)) && !resultAttributes.display.boxingWindow) { - // Outside a boxed area - resultCharacter = { 0x20, 0, 0 }; - underlined = false; - } - if ((resultCharacter.code <= 0x20 && resultCharacter.diacritical == 0 && resultCharacter.set != 26) || (resultAttributes.display.conceal && !m_reveal)) { - if (!transparentBackground) { - pixmapPainter.fillRect(c*12, r*10, charWidth, charHeight, backQColour); - } - } else { - if (transparentBackground) - pixmapPainter.setBackgroundMode(Qt::TransparentMode); - else { - pixmapPainter.setBackgroundMode(Qt::OpaqueMode); - pixmapPainter.setBackground(QBrush(backQColour)); - } - pixmapPainter.setPen(foreQColour); - pixmapPainter.drawPixmap(c*12, r*10, charWidth, charHeight, *m_fontBitmap.rawBitmap(), (resultCharacter.code-32)*12, resultCharacter.set*10, 12, 10); - if (resultCharacter.diacritical) { - pixmapPainter.setBackgroundMode(Qt::TransparentMode); - pixmapPainter.drawPixmap(c*12, r*10, charWidth, charHeight, *m_fontBitmap.rawBitmap(), 384+resultCharacter.diacritical*12, 70, 12, 10); - pixmapPainter.setBackgroundMode(Qt::OpaqueMode); - } - } - if (underlined) { - pixmapPainter.setPen(foreQColour); - if (!resultAttributes.display.doubleHeight) - pixmapPainter.drawLine(c*12, r*10+9, c*12+charWidth-1, r*10+9); - else - pixmapPainter.drawRect(c*12, r*10+18, charWidth-1, 1); - } - }; - pixmapPainter.begin(m_pagePixmap[0]); - pixmapPainter.setBackgroundMode(Qt::OpaqueMode); + return; + } else + // Transparent background, opaque foreground + pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source); + } else if (pixmapPainter.pen().color().alpha() == 0) { + // Transparent foreground, opaque background + // Deal with optimising G1 solid 7/F blocks and spaces now + // otherwise the same optimisations later on won't work with + // our tinkered QPainter settings + if (characterCode == 0x7f && characterSet == 24) { + pixmapPainter.setCompositionMode(QPainter::CompositionMode_Clear); + pixmapPainter.eraseRect(c*12, r*10, 12, 10); + pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); - m_concealRow[r] = false; - for (c=0; c<72; c++) { - resultAttributes = underlyingAttributes; - for (int l=0; lcharacter(r, c); - if (layerCharacter.code != 0x00) - resultCharacter = layerCharacter; - if (l == 0) { -// m_cell[r][c].level1Mosaic = (resultCharacter.set == 24 || resultCharacter.set == 25) && m_levelOnePage->character(r, c) >= 0x20; - m_cell[r][c].level1Mosaic = (resultCharacter.set == 24 || resultCharacter.set == 25); - if (!m_cell[r][c].level1Mosaic) - level1CharSet = resultCharacter.set; - m_cell[r][c].level1CharSet = level1CharSet; - } + return; + } - layerApplyAttributes = { false, false, false, false, false, false, false, false }; - m_textLayer[l]->attributes(r, c, &layerApplyAttributes); - if (layerApplyAttributes.copyAboveAttributes) { - resultAttributes = m_cell[r-1][c].attribute; - layerApplyAttributes.copyAboveAttributes = false; + pixmapPainter.fillRect(c*12, r*10, 12, 10, m_decoder->cellBackgroundQColor(r, c)); + + if (characterCode == 0x20 && characterSet < 25 && characterDiacritical == 0) + return; + + pixmapPainter.setBackground(QColor(0, 0, 0, 0)); + pixmapPainter.setPen(QColor(255, 255, 255, 255)); + pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut); + } + + if (characterCode == 0x20 && characterSet < 25 && characterDiacritical == 0) + pixmapPainter.fillRect(c*12, r*10, 12, 10, pixmapPainter.background().color()); + else if (characterCode == 0x7f && characterSet == 24) + pixmapPainter.fillRect(c*12, r*10, 12, 10, pixmapPainter.pen().color()); + else + drawFromFontBitmap(pixmapPainter, r, c, characterCode, characterSet, characterFragment); + + if (m_decoder->cellUnderlined(r, c) && !dontUnderline) + switch (characterFragment) { + case TeletextPageDecode::NormalSize: + case TeletextPageDecode::DoubleWidthLeftHalf: + case TeletextPageDecode::DoubleWidthRightHalf: + pixmapPainter.drawLine(c*12, r*10+9, c*12+11, r*10+9); break; - } - if (layerApplyAttributes.applyForeColour) { - resultAttributes.foreColour = layerApplyAttributes.attribute.foreColour; - if (l == 0 && m_renderLevel >= 2) - resultAttributes.foreColour |= m_foregroundRemap[m_levelOnePage->colourTableRemap()]; - } - if (layerApplyAttributes.applyBackColour) { - resultAttributes.backColour = layerApplyAttributes.attribute.backColour; - if (l == 0) { - if (m_renderLevel >= 2) - if (resultAttributes.backColour == 0x20) - resultAttributes.backColour = (c > 39 || m_levelOnePage->blackBackgroundSubst()) ? m_fullRowColour[r] : m_backgroundRemap[m_levelOnePage->colourTableRemap()]; - else - resultAttributes.backColour |= m_backgroundRemap[m_levelOnePage->colourTableRemap()]; - else - if (resultAttributes.backColour == 0x20) - resultAttributes.backColour = 0x00; - } - } - if (resultAttributes.foreColour == 8) - resultAttributes.foreColour = m_fullRowColour[r]; - if (resultAttributes.backColour == 8) - resultAttributes.backColour = m_fullRowColour[r]; - - if (layerApplyAttributes.applyFlash) { - //BUG Adaptive Objects disrupt inc/dec flash - resultAttributes.flash = layerApplyAttributes.attribute.flash; - if (resultAttributes.flash.mode != 0) - phaseNumberRender = (resultAttributes.flash.ratePhase == 4 || resultAttributes.flash.ratePhase == 5) ? 1 : resultAttributes.flash.ratePhase; - } - if (layerApplyAttributes.applyDisplayAttributes) - resultAttributes.display = layerApplyAttributes.attribute.display; - else { - // Selecting contiguous mosaics wih a triplet will override an earlier Level 1 separated mosaics attribute until a further Level 1 contiguous mosaic attribute is encountered - resultAttributes.display.forceContiguous = (layerApplyAttributes.applyContiguousOnly) ? false : underlyingAttributes.display.forceContiguous; - if (layerApplyAttributes.applyTextSizeOnly) { - resultAttributes.display.doubleHeight = layerApplyAttributes.attribute.display.doubleHeight; - resultAttributes.display.doubleWidth = layerApplyAttributes.attribute.display.doubleWidth; - } - if (layerApplyAttributes.applyBoxingOnly) - resultAttributes.display.boxingWindow = layerApplyAttributes.attribute.display.boxingWindow; - if (layerApplyAttributes.applyConcealOnly || layerApplyAttributes.applyForeColour) - resultAttributes.display.conceal = layerApplyAttributes.attribute.display.conceal; - } - if (m_textLayer[l]->objectType() <= 1) - underlyingAttributes = resultAttributes; - - if (m_renderLevel == 0) + case TeletextPageDecode::DoubleHeightBottomHalf: + case TeletextPageDecode::DoubleSizeBottomLeftQuarter: + case TeletextPageDecode::DoubleSizeBottomRightQuarter: + pixmapPainter.drawRect(c*12, r*10+8, 11, 1); + break; + default: break; } - underlined = false; - if (resultAttributes.display.underlineSeparated) { - if (resultCharacter.set == 24) - resultCharacter.set = 25; - else - underlined = resultCharacter.set < 24; - } - if (resultAttributes.display.forceContiguous && resultCharacter.set == 25) - resultCharacter.set = 24; - - resultAttributes.flash.phaseNumber = phaseNumberRender; - - previouslyDoubleHeight = m_cell[r][c].attribute.display.doubleHeight; - previouslyBottomHalf = m_cell[r][c].bottomHalf; - - m_cell[r][c].character = resultCharacter; - m_cell[r][c].attribute = resultAttributes; - - if (resultAttributes.flash.ratePhase == 4 && ++phaseNumberRender == 4) - phaseNumberRender = 1; - if (resultAttributes.flash.ratePhase == 5 && --phaseNumberRender == 0) - phaseNumberRender = 3; - if (resultAttributes.flash.mode > 0) - flashRowRequired |= (resultAttributes.flash.ratePhase == 0) ? 1 : 2; - - if (r > 0) - m_cell[r][c].bottomHalf = m_cell[r-1][c].attribute.display.doubleHeight && !m_cell[r-1][c].bottomHalf; - if ((resultAttributes.display.doubleHeight != previouslyDoubleHeight) || (m_cell[r][c].bottomHalf != previouslyBottomHalf)) - renderNextRow = true; - m_cell[r][c].rightHalf = applyRightHalf; - - if (!resultAttributes.display.invert) { - foreQColour = m_levelOnePage->CLUTtoQColor(resultAttributes.foreColour, m_renderLevel); - backQColour = m_levelOnePage->CLUTtoQColor(resultAttributes.backColour, m_renderLevel); - } else { - foreQColour = m_levelOnePage->CLUTtoQColor(resultAttributes.backColour, m_renderLevel); - backQColour = m_levelOnePage->CLUTtoQColor(resultAttributes.foreColour, m_renderLevel); - } - - renderCharacter(); - - if (m_showCodes && c < 40 && m_levelOnePage->character(r, c)<0x20 && !m_level1Layer.isRowBottomHalf(r) && !m_cell[r][c].bottomHalf) { - pixmapPainter.setBackground(QBrush(QColor(0, 0, 0, 128))); - pixmapPainter.setPen(QColor(255, 255, 255, 224)); - pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (m_levelOnePage->character(r, c)+32)*12, 250, 12, 10); - } - - if (resultAttributes.display.doubleHeight) - doubleHeightFound = true; - if (resultAttributes.display.doubleWidth) - applyRightHalf ^= true; - else - applyRightHalf = false; - if (resultAttributes.display.conceal) - m_concealRow[r] = true; - } - - pixmapPainter.end(); - - if (flashRowRequired == 3) - flashRowRequired = 2; - if (flashRowRequired != m_flashRow[r]) { - m_flashRow[r] = flashRowRequired; - updateFlashRequired(flashRowRequired); - } - // If flashing is present, copy the rendered line into the other buffers - if (m_flashRequired > 0) { - pixmapPainter.begin(m_pagePixmap[3]); - pixmapPainter.setCompositionMode(QPainter::CompositionMode_Clear); - pixmapPainter.eraseRect(0, r*10, 864, doubleHeightFound ? 20 : 10); + if (characterDiacritical != 0) { pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); - pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[0], 0, r*10, 864, doubleHeightFound ? 20 : 10); - if (m_flashRequired == 2) { - pixmapPainter.end(); - pixmapPainter.begin(m_pagePixmap[1]); - pixmapPainter.setCompositionMode(QPainter::CompositionMode_Clear); - pixmapPainter.eraseRect(0, r*10, 864, doubleHeightFound ? 20 : 10); - pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); - pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[0], 0, r*10, 864, doubleHeightFound ? 20 : 10); - pixmapPainter.end(); - pixmapPainter.begin(m_pagePixmap[2]); - pixmapPainter.setCompositionMode(QPainter::CompositionMode_Clear); - pixmapPainter.eraseRect(0, r*10, 864, doubleHeightFound ? 20 : 10); - pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); - pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[0], 0, r*10, 864, doubleHeightFound ? 20 : 10); - pixmapPainter.end(); - pixmapPainter.begin(m_pagePixmap[4]); - pixmapPainter.setCompositionMode(QPainter::CompositionMode_Clear); - pixmapPainter.eraseRect(0, r*10, 864, doubleHeightFound ? 20 : 10); - pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); - pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[3], 0, r*10, 864, doubleHeightFound ? 20 : 10); - pixmapPainter.end(); - pixmapPainter.begin(m_pagePixmap[5]); - pixmapPainter.setCompositionMode(QPainter::CompositionMode_Clear); - pixmapPainter.eraseRect(0, r*10, 864, doubleHeightFound ? 20 : 10); - pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); - pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[3], 0, r*10, 864, doubleHeightFound ? 20 : 10); + pixmapPainter.setBackgroundMode(Qt::TransparentMode); + drawFromFontBitmap(pixmapPainter, r, c, characterDiacritical+64, 7, characterFragment); + pixmapPainter.setBackgroundMode(Qt::OpaqueMode); + } + + if (pixmapPainter.compositionMode() != QPainter::CompositionMode_SourceOver) + pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); +} + +void TeletextPageRender::renderPage(bool force) +{ + QPainter pixmapPainter[6]; + int previousFlashBuffersHz = m_flashBuffersHz; + + // If force-rendering (such as showing a new or different subpage) then + // we don't render flashing cells initially as it greatly speeds things up + // when rendering on top of an already flashing page. + // At the end of this method, if flashing cells are present then this method + // recursively calls itself with force=false to render the flashing cells. + if (force) { + m_flashBuffersHz = 0; + previousFlashBuffersHz = 0; + emit flashChanged(0); + } + + pixmapPainter[0].begin(m_pagePixmap[0]); + pixmapPainter[0].setBackgroundMode(Qt::OpaqueMode); + if (m_flashBuffersHz != 0) { + pixmapPainter[3].begin(m_pagePixmap[3]); + pixmapPainter[3].setBackgroundMode(Qt::OpaqueMode); + if (m_flashBuffersHz == 2) { + pixmapPainter[1].begin(m_pagePixmap[1]); + pixmapPainter[1].setBackgroundMode(Qt::OpaqueMode); + pixmapPainter[2].begin(m_pagePixmap[2]); + pixmapPainter[2].setBackgroundMode(Qt::OpaqueMode); + pixmapPainter[4].begin(m_pagePixmap[4]); + pixmapPainter[4].setBackgroundMode(Qt::OpaqueMode); + pixmapPainter[5].begin(m_pagePixmap[5]); + pixmapPainter[5].setBackgroundMode(Qt::OpaqueMode); } - pixmapPainter.end(); - } - if (flashRowRequired) - for (c=0; c<72; c++) { - if (m_cell[r][c].attribute.flash.mode == 0 || m_cell[r][c].bottomHalf || m_cell[r][c].rightHalf) - continue; - // Compose other phase of flash, either a space or adjacent CLUT - resultAttributes = m_cell[r][c].attribute; - if (m_cell[r][c].attribute.flash.mode == 3) { - resultAttributes.foreColour ^= 8; - resultCharacter = m_cell[r][c].character; - underlined = resultAttributes.display.underlineSeparated && (m_cell[r][c].character.set < 24); - } else { // flash modes 1 and 2 - resultCharacter = { 0x20, 0, 0 }; - underlined = false; - } - if (!resultAttributes.display.invert) { - foreQColour = m_levelOnePage->CLUTtoQColor(resultAttributes.foreColour, m_renderLevel); - backQColour = m_levelOnePage->CLUTtoQColor(resultAttributes.backColour, m_renderLevel); - } else { - foreQColour = m_levelOnePage->CLUTtoQColor(resultAttributes.backColour, m_renderLevel); - backQColour = m_levelOnePage->CLUTtoQColor(resultAttributes.foreColour, m_renderLevel); - } - if (resultAttributes.flash.ratePhase == 0) { - // 1Hz flash - int flashPixmap = resultAttributes.flash.mode == 2 ? 0 : 3; - pixmapPainter.begin(m_pagePixmap[flashPixmap]); - renderCharacter(); - if (m_flashRequired == 2) { - pixmapPainter.end(); - pixmapPainter.begin(m_pagePixmap[flashPixmap+1]); - renderCharacter(); - pixmapPainter.end(); - pixmapPainter.begin(m_pagePixmap[flashPixmap+2]); - renderCharacter(); - } - pixmapPainter.end(); - } else - // 2Hz flash - for (int p=1; p<=3; p++) - if ((resultAttributes.flash.phaseNumber != p) ^ (resultAttributes.flash.mode == 2)) { - pixmapPainter.begin(m_pagePixmap[p-1]); - renderCharacter(); - pixmapPainter.end(); - pixmapPainter.begin(m_pagePixmap[p+2]); - renderCharacter(); - pixmapPainter.end(); - } - } - - if (renderNextRow && r<24) - renderPage(r+1); -} - -void TeletextPageRender::updateFlashRequired(int newFlashRequired) -{ - if (newFlashRequired == m_flashRequired) - return; - - if (newFlashRequired < m_flashRequired) { - int minimumFlashRequired = newFlashRequired; - // If the flash rate for a row is reduced, check the other rows if they still need flashing - for (int r=0; r<25; r++) - if (m_flashRow[r] > minimumFlashRequired) - minimumFlashRequired = m_flashRow[r]; - if (minimumFlashRequired > newFlashRequired) - newFlashRequired = minimumFlashRequired; - if (newFlashRequired == m_flashRequired) - return; - m_flashRequired = newFlashRequired; - emit flashChanged(m_flashRequired); - return; } - // newFlashRequired > flashRequired - if (m_flashRequired == 0) - *m_pagePixmap[3] = m_pagePixmap[0]->copy(); - if (newFlashRequired == 2) { - *m_pagePixmap[1] = m_pagePixmap[0]->copy(); - *m_pagePixmap[2] = m_pagePixmap[0]->copy(); - *m_pagePixmap[4] = m_pagePixmap[3]->copy(); - *m_pagePixmap[5] = m_pagePixmap[3]->copy(); - } - m_flashRequired = newFlashRequired; - emit flashChanged(m_flashRequired); -} + for (int r=0; r<25; r++) + for (int c=0; c<72; c++) { -inline void TeletextPageRender::setFullScreenColour(int newColour) -{ - if (m_mix || m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle)) { - m_finalFullScreenQColor = QColor(0, 0, 0, 0); - emit fullScreenColourChanged(QColor(0, 0, 0, 0)); - return; - } - QColor newFullScreenQColor = m_levelOnePage->CLUTtoQColor(newColour, m_renderLevel); - m_finalFullScreenColour = newColour; - if (m_finalFullScreenQColor != newFullScreenQColor) { - m_finalFullScreenQColor = newFullScreenQColor; - emit fullScreenColourChanged(m_finalFullScreenQColor); - } -} + bool controlCodeChanged = false; -inline void TeletextPageRender::setFullRowColour(int row, int newColour) -{ - if (m_mix || m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle)) { - m_fullRowQColor[row] = QColor(0, 0, 0, 0); - emit fullRowColourChanged(row, QColor(0, 0, 0, 0)); - return; - } - QColor newFullRowQColor = m_levelOnePage->CLUTtoQColor(newColour, m_renderLevel); - m_fullRowColour[row] = newColour; - if (m_fullRowQColor[row] != newFullRowQColor) { - m_fullRowQColor[row] = newFullRowQColor; - emit fullRowColourChanged(row, m_fullRowQColor[row]); - } -} - -void TextLayer::setTeletextPage(LevelOnePage *newCurrentPage) { m_levelOnePage = newCurrentPage; } -void TextLayer::setFullScreenColour(int newColour) { m_layerFullScreenColour = newColour; } - -void TextLayer::setFullRowColour(int r, int newColour, bool newDownwards) -{ - m_layerFullRowColour[r] = newColour; - m_layerFullRowDownwards[r] = newDownwards; -} - -void EnhanceLayer::setObjectType(int newObjectType) { m_objectType = newObjectType; } - -void EnhanceLayer::setOrigin(int r, int c) -{ - m_originR = r; - m_originC = c; -} - -Level1Layer::Level1Layer() -{ - for (int r=0; r<25; r++) { - m_rowHasDoubleHeightAttr[r] = false; - m_rowHeight[r] = RHnormal; - } -} - -EnhanceLayer::EnhanceLayer() -{ - for (int r=0; r<25; r++) { - m_layerFullRowColour[r] = -1; - m_layerFullRowDownwards[r] = false; - } -} - -textCharacter EnhanceLayer::character(int r, int c) -{ - r -= m_originR; - c -= m_originC; - if (r < 0 || c < 0) - return { 0, 0 }; - - // QPair.first is triplet mode, QPair.second is triplet data - QList> enhancements = enhanceMap.values(qMakePair(r, c)); - - if (enhancements.size() > 0) - for (int i=0; iteletextPage()->character(r, c) < 0x20)) { + controlCodeChanged = m_controlCodeCache[r][c] != m_decoder->teletextPage()->character(r, c); + if (controlCodeChanged) { + if (m_decoder->teletextPage()->character(r, c) < 0x20) + m_controlCodeCache[r][c] = m_decoder->teletextPage()->character(r, c); else - return { enhancements.at(i).second, 0, enhancements.at(i).first & 0x0f }; - case 0x21: // G1 character - if ((enhancements.at(i).second) >= 0x20) - return { enhancements.at(i).second, (enhancements.at(i).second & 0x20) ? 24 : 0 }; + m_controlCodeCache[r][c] = 0x7f; + } } - return { 0, 0 }; + + if (m_decoder->refresh(r, c) || force || controlCodeChanged) { + unsigned char characterCode; + int characterSet, characterDiacritical; + + if (!m_reveal && m_decoder->cellConceal(r, c)) { + characterCode = 0x20; + characterSet = 0; + characterDiacritical = 0; + } else { + characterCode = m_decoder->cellCharacterCode(r, c); + characterSet = m_decoder->cellCharacterSet(r, c); + characterDiacritical = m_decoder->cellCharacterDiacritical(r, c); + } + + // QSet::insert won't insert a duplicate value already in the set + // QSet::remove doesn't mind if we try to remove a value that's not there + if (m_flashBuffersHz == 0 && m_decoder->cellFlashMode(r, c) != 0) { + if (m_decoder->cellFlashRatePhase(r, c) == 0) + m_flash1HzCells.insert(qMakePair(r, c)); + else + m_flash2HzCells.insert(qMakePair(r, c)); + if (!force) + updateFlashBuffers(); + } else if (m_decoder->cellFlashMode(r, c) == 0) { + m_flash1HzCells.remove(qMakePair(r, c)); + m_flash2HzCells.remove(qMakePair(r, c)); + if (!force) + updateFlashBuffers(); + } else if (m_decoder->cellFlashRatePhase(r, c) == 0) { + m_flash1HzCells.insert(qMakePair(r, c)); + m_flash2HzCells.remove(qMakePair(r, c)); + if (!force) + updateFlashBuffers(); + } else { + m_flash1HzCells.remove(qMakePair(r, c)); + m_flash2HzCells.insert(qMakePair(r, c)); + if (!force) + updateFlashBuffers(); + } + + // If flash rate has gone up, prepare painters for the other buffers + if (m_flashBuffersHz > previousFlashBuffersHz) { + if (previousFlashBuffersHz == 0) { + pixmapPainter[3].begin(m_pagePixmap[3]); + pixmapPainter[3].setBackgroundMode(Qt::OpaqueMode); + } + if (m_flashBuffersHz == 2) { + pixmapPainter[1].begin(m_pagePixmap[1]); + pixmapPainter[1].setBackgroundMode(Qt::OpaqueMode); + pixmapPainter[2].begin(m_pagePixmap[2]); + pixmapPainter[2].setBackgroundMode(Qt::OpaqueMode); + pixmapPainter[4].begin(m_pagePixmap[4]); + pixmapPainter[4].setBackgroundMode(Qt::OpaqueMode); + pixmapPainter[5].begin(m_pagePixmap[5]); + pixmapPainter[5].setBackgroundMode(Qt::OpaqueMode); + } + previousFlashBuffersHz = m_flashBuffersHz; + } + + // If flash rate has gone down, end the painters so we don't crash + // if the pixmaps get copied due to the flash rate going up again + if (m_flashBuffersHz < previousFlashBuffersHz) { + if (previousFlashBuffersHz == 2) { + pixmapPainter[1].end(); + pixmapPainter[2].end(); + pixmapPainter[4].end(); + pixmapPainter[5].end(); + } + if (m_flashBuffersHz == 0) + pixmapPainter[3].end(); + + previousFlashBuffersHz = m_flashBuffersHz; + } + + if (m_flashBuffersHz == 0) { + pixmapPainter[0].setPen(m_decoder->cellForegroundQColor(r, c)); + if (!m_mix || m_decoder->cellBoxed(r, c)) + pixmapPainter[0].setBackground(m_decoder->cellBackgroundQColor(r, c)); + else + pixmapPainter[0].setBackground(Qt::transparent); + drawCharacter(pixmapPainter[0], r, c, characterCode, characterSet, characterDiacritical, m_decoder->cellCharacterFragment(r, c)); + } else { + for (int i=0; i<6; i++) { + if (m_flashBuffersHz == 1 && (i == 1 || i == 2 || i == 4 || i == 5)) + continue; + + bool phaseOn; + + if (m_decoder->cellFlashRatePhase(r, c) == 0) + phaseOn = (i < 3) ^ (m_decoder->cellFlashMode(r, c) == 2); + else + phaseOn = ((i == m_decoder->cellFlash2HzPhaseNumber(r, c)-1) || (i == m_decoder->cellFlash2HzPhaseNumber(r, c)+2)) ^ (m_decoder->cellFlashMode(r, c) == 2); + + if (!m_mix || m_decoder->cellBoxed(r, c)) + pixmapPainter[i].setBackground(m_decoder->cellBackgroundQColor(r, c)); + else + pixmapPainter[i].setBackground(Qt::transparent); + if (m_decoder->cellFlashMode(r, c) == 3 && !phaseOn) + pixmapPainter[i].setPen(m_decoder->cellFlashForegroundQColor(r, c)); + else + pixmapPainter[i].setPen(m_decoder->cellForegroundQColor(r, c)); + if ((m_decoder->cellFlashMode(r, c) == 1 || m_decoder->cellFlashMode(r, c) == 2) && !phaseOn) + // Character 0x00 draws space without underline + drawCharacter(pixmapPainter[i], r, c, 0x00, 0, 0, m_decoder->cellCharacterFragment(r, c)); + else + drawCharacter(pixmapPainter[i], r, c, characterCode, characterSet, characterDiacritical, m_decoder->cellCharacterFragment(r, c)); + } + } + + if (m_showControlCodes && c < 40 && m_decoder->teletextPage()->character(r, c) < 0x20) { + pixmapPainter[0].setBackground(QColor(0, 0, 0, 128)); + pixmapPainter[0].setPen(QColor(255, 255, 255, 224)); + pixmapPainter[0].drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10); + if (m_flashBuffersHz == 1) { + pixmapPainter[3].setBackground(QColor(0, 0, 0, 128)); + pixmapPainter[3].setPen(QColor(255, 255, 255, 224)); + pixmapPainter[3].drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10); + } else if (m_flashBuffersHz == 2) + for (int i=1; i<6; i++) { + pixmapPainter[i].setBackground(QColor(0, 0, 0, 128)); + pixmapPainter[i].setPen(QColor(255, 255, 255, 224)); + pixmapPainter[i].drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10); + } + } + + if (!force || m_decoder->cellFlashMode(r, c) == 0) + m_decoder->setRefresh(r, c, false); + } + } + + pixmapPainter[0].end(); + if (m_flashBuffersHz != 0) { + pixmapPainter[3].end(); + if (m_flashBuffersHz == 2) { + pixmapPainter[1].end(); + pixmapPainter[2].end(); + pixmapPainter[4].end(); + pixmapPainter[5].end(); + } + } + + if (force && (!m_flash1HzCells.isEmpty() || !m_flash2HzCells.isEmpty())) + renderPage(); } -void EnhanceLayer::attributes(int r, int c, applyAttributes *layerApplyAttributes) +void TeletextPageRender::updateFlashBuffers() { - r -= m_originR; - c -= m_originC; - if (r < 0 || c < 0) - return; - if (m_objectType == 2) { - // Adaptive Object - find rightmost column addressed on this row if we haven't already - if (r != m_rowCached) { - m_rightMostColumn[r] = 0; - m_rowCached = r; - for (int cc=39; cc>0; cc--) - if (enhanceMap.contains(qMakePair(r, cc))) { - m_rightMostColumn[r] = cc; - break; - } - } - // On new row, default to attributes already on page - // At end of rightmost column, let go of all attributes - if (c == 0 || c == m_rightMostColumn[r]+1) - m_applyAttributes = { false, false, false, false, false, false, false, false }; - else { - // Re-apply attributes that Object has defined previously on this row - if (m_applyAttributes.applyForeColour) { - layerApplyAttributes->applyForeColour = true; - layerApplyAttributes->attribute.foreColour = m_applyAttributes.attribute.foreColour; - } - if (m_applyAttributes.applyBackColour) { - layerApplyAttributes->applyBackColour = true; - layerApplyAttributes->attribute.backColour = m_applyAttributes.attribute.backColour; - } - //BUG Adaptive Objects disrupt inc/dec flash - if (m_applyAttributes.applyFlash) { - layerApplyAttributes->applyFlash = true; - layerApplyAttributes->attribute.flash.mode = m_applyAttributes.attribute.flash.mode; - layerApplyAttributes->attribute.flash.ratePhase = m_applyAttributes.attribute.flash.ratePhase; - } - if (m_applyAttributes.applyDisplayAttributes) { - layerApplyAttributes->applyDisplayAttributes = true; - layerApplyAttributes->attribute.display = m_applyAttributes.attribute.display; - } - } - } - if (m_objectType == 3) { - if (r == 0 && c == 0) { - // Passive Objects always start with all these default attributes - m_applyAttributes.applyForeColour = true; - m_applyAttributes.attribute.foreColour = 0x07; - m_applyAttributes.applyBackColour = true; - m_applyAttributes.attribute.backColour = 0x00; - m_applyAttributes.applyDisplayAttributes = true; - m_applyAttributes.applyFlash = true; - m_applyAttributes.attribute.flash.mode = 0; - m_applyAttributes.attribute.flash.ratePhase = 0; - m_applyAttributes.attribute.display.doubleHeight = false; - m_applyAttributes.attribute.display.doubleWidth = false; - m_applyAttributes.attribute.display.boxingWindow = false; - m_applyAttributes.attribute.display.conceal = false; - m_applyAttributes.attribute.display.invert = false; - m_applyAttributes.attribute.display.underlineSeparated = false; - m_applyAttributes.attribute.display.forceContiguous = false; - } - if (character(r+m_originR, c+m_originC).code == 0x00) - // Passive Object attributes only apply where it also defines a character - // In this case, wrench the pointer-parameter to alter only the attributes of the Object - layerApplyAttributes = &m_applyAttributes; - else - *layerApplyAttributes = m_applyAttributes; - } + int highestFlashHz; - // QPair.first is triplet mode, QPair.second is triplet data - QList> enhancements = enhanceMap.values(qMakePair(r, c)); - - for (int i=0; iapplyForeColour = true; - layerApplyAttributes->attribute.foreColour = enhancements.at(i).second; - } - break; - case 0x23: // Background colour - if ((enhancements.at(i).second & 0x60) == 0) { - layerApplyAttributes->applyBackColour = true; - layerApplyAttributes->attribute.backColour = enhancements.at(i).second; - } - break; - case 0x27: // Additional flash functions - if ((enhancements.at(i).second & 0x60) == 0 && (enhancements.at(i).second & 0x18) != 0x18) { // Avoid reserved rate/phase - layerApplyAttributes->applyFlash = true; - layerApplyAttributes->attribute.flash.mode = enhancements.at(i).second & 0x03; - layerApplyAttributes->attribute.flash.ratePhase = (enhancements.at(i).second >> 2) & 0x07; - } - break; - case 0x2c: // Display attributes - layerApplyAttributes->applyDisplayAttributes = true; - layerApplyAttributes->attribute.display.doubleHeight = enhancements.at(i).second & 0x01; - layerApplyAttributes->attribute.display.boxingWindow = enhancements.at(i).second & 0x02; - layerApplyAttributes->attribute.display.conceal = enhancements.at(i).second & 0x04; - layerApplyAttributes->attribute.display.invert = enhancements.at(i).second & 0x10; - layerApplyAttributes->attribute.display.underlineSeparated = enhancements.at(i).second & 0x20; - // Selecting contiguous mosaics wih a triplet will override an earlier Level 1 separated mosaics attribute - layerApplyAttributes->attribute.display.forceContiguous = !layerApplyAttributes->attribute.display.underlineSeparated; - layerApplyAttributes->attribute.display.doubleWidth = enhancements.at(i).second & 0x40; - break; - } - if (m_objectType >= 2) - m_applyAttributes = *layerApplyAttributes; -} - - -void Level1Layer::updateRowCache(int r) -{ - level1CacheAttributes buildCacheAttributes; - bool doubleHeightAttrFound = false; - - for (int c=0; c<40; c++) { - unsigned char charCode = m_levelOnePage->character(r, c); - // Set at spacing attributes - switch (charCode) { - case 0x0c: // Normal size - if (buildCacheAttributes.sizeCode != 0x0c) // Size CHANGE resets held mosaic to space - buildCacheAttributes.holdChar = 0x20; - buildCacheAttributes.sizeCode = 0x0c; - break; - case 0x19: // Contiguous mosaics - buildCacheAttributes.separated = false; - break; - case 0x1a: // Separated mosaics - buildCacheAttributes.separated = true; - break; - case 0x1c: // Black background - buildCacheAttributes.backColour = 0x00; - break; - case 0x1d: // New background - buildCacheAttributes.backColour = buildCacheAttributes.foreColour & 0x07; - break; - case 0x1e: // Hold mosaics - buildCacheAttributes.held = true; - break; - } - - if (buildCacheAttributes.mosaics && (charCode & 0x20)) { - buildCacheAttributes.holdChar = charCode; - buildCacheAttributes.holdSeparated = buildCacheAttributes.separated; - } - - m_attributeCache[c] = buildCacheAttributes; - - // Set-after spacing attributes - switch (charCode) { - case 0x00 ... 0x07: // Alphanumeric + foreground colour - buildCacheAttributes.foreColour = charCode; - buildCacheAttributes.mosaics = false; - buildCacheAttributes.holdChar = 0x20; // Switch from mosaics to alpha resets held mosaic - buildCacheAttributes.holdSeparated = false; - break; - case 0x10 ... 0x17: // Mosaic + foreground colour - buildCacheAttributes.foreColour = charCode & 0x07; - buildCacheAttributes.mosaics = true; - break; - case 0x0d: // Double height - case 0x0f: // Double size - doubleHeightAttrFound = true; - // fall-through - case 0x0e: // Double width - if (buildCacheAttributes.sizeCode != charCode) // Size CHANGE resets held mosaic to space - buildCacheAttributes.holdChar = 0x20; - buildCacheAttributes.sizeCode = charCode; - break; - case 0x1b: // ESC/switch - buildCacheAttributes.escSwitch ^= true; - break; - case 0x1f: // Release mosaics - buildCacheAttributes.held = false; - break; - } - } - - if (doubleHeightAttrFound != m_rowHasDoubleHeightAttr[r]) { - m_rowHasDoubleHeightAttr[r] = doubleHeightAttrFound; - for (int dr=r; dr<24; dr++) - if (m_rowHasDoubleHeightAttr[dr]) { - m_rowHeight[dr] = RHtophalf; - m_rowHeight[++dr] = RHbottomhalf; - } else - m_rowHeight[dr] = RHnormal; - } -} - -textCharacter Level1Layer::character(int r, int c) -{ - textCharacter result; - - if (r != m_rowCached) - updateRowCache(r); - if (c > 39 || m_rowHeight[r] == RHbottomhalf) - return { 0x20, 0 }; - result.code = m_levelOnePage->character(r, c); - if (m_levelOnePage->secondCharSet() != 0xf && m_attributeCache[c].escSwitch) - result.set = g0CharacterMap.value(((m_levelOnePage->secondCharSet() << 3) | m_levelOnePage->secondNOS()), 0); + if (!m_flash2HzCells.isEmpty()) + highestFlashHz = 2; else - result.set = g0CharacterMap.value(((m_levelOnePage->defaultCharSet() << 3) | m_levelOnePage->defaultNOS()), 0); - if (result.code < 0x20) { - result.code = m_attributeCache[c].held ? m_attributeCache[c].holdChar : 0x20; - if (m_attributeCache[c].held && c > 0) - result.set = 24+m_attributeCache[c].holdSeparated; -// else -// result.set = m_attributeCache[c].mosaics*24; - } else if (m_attributeCache[c].mosaics && (result.code & 0x20)) - result.set = 24+m_attributeCache[c].separated; - return result; -} + highestFlashHz = !m_flash1HzCells.isEmpty(); -void Level1Layer::attributes(int r, int c, applyAttributes *layerApplyAttributes) -{ - unsigned char characterCode; + if (highestFlashHz == m_flashBuffersHz) + return; - if (m_rowHeight[r] == RHbottomhalf) { - layerApplyAttributes->copyAboveAttributes = true; - return; - } - if (r != m_rowCached) - updateRowCache(r); - if (c == 0 || c == 40 || c == 56) { - // Start of row default conditions, also when crossing into side panels - layerApplyAttributes->applyForeColour = true; - layerApplyAttributes->attribute.foreColour = 0x07; - layerApplyAttributes->applyBackColour = true; - layerApplyAttributes->attribute.backColour = 0x20; - layerApplyAttributes->applyDisplayAttributes = true; - layerApplyAttributes->applyFlash = true; - layerApplyAttributes->attribute.flash.mode = 0; - layerApplyAttributes->attribute.flash.ratePhase = 0; - layerApplyAttributes->attribute.display.doubleHeight = false; - layerApplyAttributes->attribute.display.doubleWidth = false; - layerApplyAttributes->attribute.display.boxingWindow = false; - layerApplyAttributes->attribute.display.conceal = false; - layerApplyAttributes->attribute.display.invert = false; - layerApplyAttributes->attribute.display.underlineSeparated = false; - layerApplyAttributes->attribute.display.forceContiguous = false; - //TODO fontstyle - } - if (c > 39) - return; - if (c > 0) { - // Set-after - characterCode = m_levelOnePage->character(r, c-1); - switch (characterCode) { - case 0x00 ... 0x07: // Alphanumeric + Foreground colour - case 0x10 ... 0x17: // Mosaic + Foreground colour - layerApplyAttributes->applyForeColour = true; - layerApplyAttributes->attribute.foreColour = characterCode & 0x07; - layerApplyAttributes->attribute.display.conceal = false; - break; - case 0x08: // Flashing - layerApplyAttributes->applyFlash = true; - layerApplyAttributes->attribute.flash.mode = 1; - layerApplyAttributes->attribute.flash.ratePhase = 0; - break; - case 0x0a: // End box - if (m_levelOnePage->character(r, c) == 0x0a) { - layerApplyAttributes->applyBoxingOnly = true; - layerApplyAttributes->attribute.display.boxingWindow = false; - } - break; - case 0x0b: // Start box - if (m_levelOnePage->character(r, c) == 0x0b) { - layerApplyAttributes->applyBoxingOnly = true; - layerApplyAttributes->attribute.display.boxingWindow = true; - } - break; - case 0x0d: // Double height - layerApplyAttributes->applyTextSizeOnly = true; - layerApplyAttributes->attribute.display.doubleHeight = true; - layerApplyAttributes->attribute.display.doubleWidth = false; - break; - case 0x0e: // Double width - layerApplyAttributes->applyTextSizeOnly = true; - layerApplyAttributes->attribute.display.doubleHeight = false; - layerApplyAttributes->attribute.display.doubleWidth = true; - break; - case 0x0f: // Double size - layerApplyAttributes->applyTextSizeOnly = true; - layerApplyAttributes->attribute.display.doubleHeight = true; - layerApplyAttributes->attribute.display.doubleWidth = true; - break; + if (highestFlashHz > m_flashBuffersHz) { + if (m_flashBuffersHz == 0) + *m_pagePixmap[3] = m_pagePixmap[0]->copy(); + if (highestFlashHz == 2) { + *m_pagePixmap[1] = m_pagePixmap[0]->copy(); + *m_pagePixmap[2] = m_pagePixmap[0]->copy(); + *m_pagePixmap[4] = m_pagePixmap[3]->copy(); + *m_pagePixmap[5] = m_pagePixmap[3]->copy(); } } - // Set-at - characterCode = m_levelOnePage->character(r, c); - switch (characterCode) { - case 0x09: // Steady - layerApplyAttributes->applyFlash = true; - layerApplyAttributes->attribute.flash.mode = 0; - layerApplyAttributes->attribute.flash.ratePhase = 0; - break; - case 0x0c: // Normal size - layerApplyAttributes->applyTextSizeOnly = true; - layerApplyAttributes->attribute.display.doubleHeight = false; - layerApplyAttributes->attribute.display.doubleWidth = false; - break; - case 0x18: // Conceal - layerApplyAttributes->applyConcealOnly = true; - layerApplyAttributes->attribute.display.conceal = true; - break; - case 0x19: // Contiguous mosaics - layerApplyAttributes->applyContiguousOnly = true; - break; - case 0x1c: // Black background - layerApplyAttributes->applyBackColour = true; - layerApplyAttributes->attribute.backColour = 0x20; - break; - case 0x1d: // New background - layerApplyAttributes->applyBackColour = true; - layerApplyAttributes->attribute.backColour = m_attributeCache[c].backColour; - break; - } + + m_flashBuffersHz = highestFlashHz; + emit flashChanged(m_flashBuffersHz); +} + +void TeletextPageRender::colourChanged(int index) +{ + for (int r=0; r<25; r++) + for (int c=0; c<72; c++) { + if (m_decoder->cellForegroundCLUT(r, c) == index || m_decoder->cellBackgroundCLUT(r, c) == index || m_decoder->cellForegroundCLUT(r, c) == 8 || m_decoder->cellBackgroundCLUT(r, c) == 8) + m_decoder->setRefresh(r, c, true); + if (m_decoder->cellFlashMode(r, c) == 3 && ((m_decoder->cellForegroundCLUT(r, c) ^ 8) == index || (m_decoder->cellForegroundCLUT(r, c) ^ 8) == 8)) + m_decoder->setRefresh(r, c, true); + } +} + +void TeletextPageRender::setReveal(bool reveal) +{ + if (reveal == m_reveal) + return; + + m_reveal = reveal; + + for (int r=0; r<25; r++) + for (int c=0; c<72; c++) + if (m_decoder->cellConceal(r, c)) + m_decoder->setRefresh(r, c, true); +} + +void TeletextPageRender::setMix(bool mix) +{ + if (mix == m_mix) + return; + + m_mix = mix; + + for (int r=0; r<25; r++) + for (int c=0; c<72; c++) + if (!m_decoder->cellBoxed(r, c)) + m_decoder->setRefresh(r, c, true); } -ActivePosition::ActivePosition() +void TeletextPageRender::setShowControlCodes(bool showControlCodes) { - m_row = m_column = -1; -} + if (showControlCodes == m_showControlCodes) + return; -bool ActivePosition::setRow(int newRow) -{ - if (newRow < m_row) - return false; - if (newRow > m_row) { - m_row = newRow; - m_column = -1; - } - return true; -} + m_showControlCodes = showControlCodes; -bool ActivePosition::setColumn(int newColumn) -{ - if (newColumn < m_column) - return false; - if (m_row == -1) - m_row = 0; - m_column = newColumn; - return true; + for (int r=0; r<25; r++) + for (int c=0; c<40; c++) + if (m_decoder->teletextPage()->character(r, c) < 0x20) + m_decoder->setRefresh(r, c, true); } -/* -bool ActivePosition::setRowAndColumn(int newRow, int newColumn) -{ - if (!setRow(newRow)) - return false; - return setColumn(newColumn); -} -*/ diff --git a/render.h b/render.h index 3bc1877..7efa7a2 100644 --- a/render.h +++ b/render.h @@ -21,169 +21,17 @@ #define RENDER_H #include -#include -#include -#include -#include +#include +#include -#include "levelonepage.h" - - -struct textCharacter { - unsigned char code=0x20; - int set=0; - int diacritical=0; -}; - -struct displayAttributes { - bool doubleHeight=false; - bool doubleWidth=false; - bool boxingWindow=false; - bool conceal=false; - bool invert=false; - bool underlineSeparated=false; - bool forceContiguous=false; -}; - -struct textAttributes { - int foreColour=0x07; - int backColour=0x00; - struct flashFunctions { - int mode=0; - int ratePhase=0; - int phaseNumber=0; - } flash; - displayAttributes display; - /* font style */ -}; - -struct textCell { - textCharacter character; - textAttributes attribute; - bool bottomHalf=false; - bool rightHalf=false; - bool level1Mosaic=false; - int level1CharSet=0; -}; - -struct applyAttributes { - bool applyForeColour=false; - bool applyBackColour=false; - bool applyFlash=false; - bool applyDisplayAttributes=false; - bool applyTextSizeOnly=false; - bool applyBoxingOnly=false; - bool applyConcealOnly=false; - bool applyContiguousOnly=false; - bool copyAboveAttributes=false; - textAttributes attribute; -}; - -class ActivePosition -{ -public: - ActivePosition(); - int row() const { return (m_row == -1) ? 0 : m_row; } - int column() const { return (m_column == -1) ? 0 : m_column; } - bool isDeployed() const { return m_row != -1; } - bool setRow(int); - bool setColumn(int); -// bool setRowAndColumn(int, int); - -private: - int m_row, m_column; -}; - - -class TextLayer -{ -public: -// TextLayer(TeletextPage* thePage) : currentPage(thePage) { }; - virtual ~TextLayer() = default; - void setTeletextPage(LevelOnePage *); - virtual textCharacter character(int, int) =0; - virtual void attributes(int, int, applyAttributes *) =0; - virtual int fullScreenColour() const =0; - virtual int fullRowColour(int) const =0; - virtual bool fullRowDownwards(int) const =0; - virtual int objectType() const =0; - virtual int originR() const { return 0; }; - virtual int originC() const { return 0; }; - void setFullScreenColour(int); - void setFullRowColour(int, int, bool); - - // Key QPair is row and column, value QPair is triplet mode and data - QMultiMap, QPair> enhanceMap; - -protected: - LevelOnePage* m_levelOnePage; - int m_layerFullScreenColour=-1; - int m_layerFullRowColour[25]; - bool m_layerFullRowDownwards[25]; - applyAttributes m_applyAttributes; -}; - -class EnhanceLayer: public TextLayer -{ -public: - EnhanceLayer(); - textCharacter character(int, int); - void attributes(int, int, applyAttributes *); - int fullScreenColour() const { return m_layerFullScreenColour; }; - int fullRowColour(int r) const { return m_layerFullRowColour[r]; }; - bool fullRowDownwards(int r) const { return m_layerFullRowDownwards[r]; }; - int objectType() const { return m_objectType; }; - int originR() const { return m_originR; }; - int originC() const { return m_originC; }; - void setObjectType(int); - void setOrigin(int, int); - -protected: - int m_objectType=0; - int m_originR=0; - int m_originC=0; - int m_rowCached=-1; - int m_rightMostColumn[25]; -}; - -class Level1Layer: public TextLayer -{ -public: -// Level1Layer(TeletextPage *thePage) : TextLayer(thePage) { }; - Level1Layer(); - textCharacter character(int, int); - void attributes(int, int, applyAttributes *); - int fullScreenColour() const { return -1; }; - int fullRowColour(int) const { return -1; }; - bool fullRowDownwards(int) const { return false; }; - int objectType() const { return 0; } - bool isRowBottomHalf(int r) const { return m_rowHeight[r]==RHbottomhalf; } - -private: - void updateRowCache(int); - - struct level1CacheAttributes { - int foreColour=0x07; - int backColour=0x00; - unsigned char sizeCode=0x0c; - bool mosaics=false; - bool separated=false; - bool held=false; - bool escSwitch=false; - unsigned char holdChar=0x20; - bool holdSeparated=false; - }; - level1CacheAttributes m_attributeCache[40]; - int m_rowCached=-1; - bool m_rowHasDoubleHeightAttr[25]; - enum rowHeightEnum { RHnormal=-1, RHtophalf, RHbottomhalf } m_rowHeight[25]; -}; +#include "decode.h" class TeletextFontBitmap { public: TeletextFontBitmap(); ~TeletextFontBitmap(); + QBitmap *rawBitmap() const { return s_fontBitmap; } private: @@ -198,67 +46,38 @@ class TeletextPageRender : public QObject public: TeletextPageRender(); ~TeletextPageRender(); - void decodePage(); - void renderPage(); - void renderPage(int r); - bool mix() const { return m_mix; }; - bool showCodes() const { return m_showCodes; }; - void setTeletextPage(LevelOnePage *); - void updateSidePanels(); - void buildEnhanceMap(TextLayer *, int=0); + QPixmap* pagePixmap(int i) const { return m_pagePixmap[i]; }; - bool level1MosaicAttribute(int r, int c) const { return m_cell[r][c].level1Mosaic; }; - int level1CharSet(int r, int c) const { return m_cell[r][c].level1CharSet; }; - int leftSidePanelColumns() const { return m_leftSidePanelColumns; }; - int rightSidePanelColumns() const { return m_rightSidePanelColumns; }; + bool mix() const { return m_mix; }; + void setDecoder(TeletextPageDecode *); + void renderPage(bool force=false); + bool showControlCodes() const { return m_showControlCodes; }; public slots: + void colourChanged(int); void setReveal(bool); void setMix(bool); - void setShowCodes(bool); - void setRenderLevel(int); + void setShowControlCodes(bool); signals: - void fullScreenColourChanged(QColor); - void fullRowColourChanged(int, QColor); void flashChanged(int); - void sidePanelsChanged(); protected: - void updateFlashRequired(int); - inline void setFullScreenColour(int); - inline void setFullRowColour(int, int); - TeletextFontBitmap m_fontBitmap; QPixmap* m_pagePixmap[6]; - int m_finalFullScreenColour, m_renderLevel; - QColor m_finalFullScreenQColor; - int m_leftSidePanelColumns, m_rightSidePanelColumns; - bool m_reveal, m_mix, m_showCodes; - Level1Layer m_level1Layer; - std::vector m_textLayer; - const int m_foregroundRemap[8] = { 0, 0, 0, 8, 8, 16, 16, 16 }; - const int m_backgroundRemap[8] = { 0, 8, 16, 8, 16, 8, 16, 24 }; + textCell m_cell[25][72]; + unsigned char m_controlCodeCache[25][40]; + bool m_reveal, m_mix, m_showControlCodes; + QSet> m_flash1HzCells; + QSet> m_flash2HzCells; + int m_flashBuffersHz; private: - textCell m_cell[25][72]; - LevelOnePage* m_levelOnePage; - int m_flashRequired; - int m_fullRowColour[25]; - QColor m_fullRowQColor[25]; - int m_flashRow[25]; - bool m_concealRow[25]; -}; + inline void drawFromFontBitmap(QPainter &, int, int, unsigned char, int, TeletextPageDecode::CharacterFragment); + inline void drawCharacter(QPainter &, int, int, unsigned char, int, int, TeletextPageDecode::CharacterFragment); + void updateFlashBuffers(); -static const QMap g0CharacterMap { - { 0x00, 12 }, { 0x01, 15 }, { 0x02, 22 }, { 0x03, 16 }, { 0x04, 14 }, { 0x05, 19 }, { 0x06, 11 }, - { 0x08, 18 }, { 0x09, 15 }, { 0x0a, 22 }, { 0x0b, 16 }, { 0x0c, 14 }, { 0x0e, 11 }, - { 0x10, 12 }, { 0x11, 15 }, { 0x12, 22 }, { 0x13, 16 }, { 0x14, 14 }, { 0x15, 19 }, { 0x16, 23 }, - { 0x1d, 21 }, { 0x1f, 20 }, - { 0x20, 1 }, { 0x21, 15 }, { 0x22, 13 }, { 0x23, 17 }, { 0x24, 2 }, { 0x25, 3 }, { 0x26, 11 }, - { 0x36, 23 }, { 0x37, 4 }, - { 0x40, 12 }, { 0x44, 14 }, { 0x47, 5 }, - { 0x55, 6 }, { 0x57, 5 } + TeletextPageDecode *m_decoder; }; #endif