#include #include "printHelper.h" #include "nibblehelpers.h" #include "graphics.h" #include "vm.h" #include "Audio.h" PicoRam* _ph_mem; Graphics* _ph_graphics; Vm* _ph_vm; Audio* _ph_audio; void hexStrToBytes(std::string hex, uint8_t byteBuff[]) { char buff[3]; buff[2] = 0; for (unsigned int i = 0; i < hex.length(); i += 2) { buff[0] = hex[i]; buff[1] = hex[i+1]; uint8_t byte = (uint8_t) strtol(buff, NULL, 16); byteBuff[i/2] = byte; } } void initPrintHelper(PicoRam* memory, Graphics* graphics, Vm* vm, Audio* audio) { _ph_mem = memory; _ph_graphics = graphics; _ph_vm = vm; _ph_audio = audio; } uint8_t p0CharToNum(uint8_t hexChar){ uint8_t num = 0; if (hexChar > 47 && hexChar < 58){ num = hexChar - 48; } if (hexChar > 96) { num = hexChar - 87; } return num; } int pow2(int power){ switch(power){ case 0: return 1; break; case 1: return 2; break; case 2: return 4; break; case 3: return 8; break; case 4: return 16; break; case 5: return 32; break; case 6: return 64; break; case 7: return 128; break; case 8: return 255; break; } return 0; } int print(std::string str) { //todo: default is not 0,0? int x = _ph_mem->drawState.text_x; int y = _ph_mem->drawState.text_y; if (y >= 127) { //Memcy screen to itself offset by how many lines needed (y + line height) int lineHeight = 6; // possibly need to check if bigger font? int linesToCopy = 127 - lineHeight; int startY = y - linesToCopy; memmove( &_ph_mem->screenBuffer, &_ph_mem->screenBuffer[COMBINED_IDX(0, startY)], linesToCopy * 64); //set the rest of the screen buffer to 0 (black) memset( &_ph_mem->screenBuffer[COMBINED_IDX(0, linesToCopy)], 0, startY * 64); y = _ph_mem->drawState.text_y = 127 - lineHeight; //Memcpy buffy back to screen } int result = print(str, x, y); if (_ph_mem->drawState.text_y >= 127) { int lineHeight = 5; // possibly need to check if bigger font? int linesToCopy = 127 - lineHeight; int startY = _ph_mem->drawState.text_y - linesToCopy; memmove( &_ph_mem->screenBuffer, &_ph_mem->screenBuffer[COMBINED_IDX(0, startY)], linesToCopy * 64); //set the rest of the screen buffer to 0 (black) memset( &_ph_mem->screenBuffer[COMBINED_IDX(0, linesToCopy)], 0, startY * 64); y = _ph_mem->drawState.text_y = 127 - lineHeight; } return result; } int print(std::string str, int x, int y) { return print(str, x, y, _ph_mem->drawState.color); } int print(std::string str, int x, int y, uint8_t c) { _ph_graphics->color(c); _ph_mem->drawState.text_x = x; _ph_mem->drawState.text_y = y; int length = str.length(); int homeX = x; int homeY = y; int prevX = x; int prevY = y; int tabStopWidth = 4; int charWidth = 4; int charHeight = 6; int lineHeight = 0; int forceCharWidth = -1; int forceCharHeight = -1; uint8_t bgColor = 0xff; uint8_t fgColor = _ph_mem->drawState.color; uint8_t charBytes[8]; uint8_t audioStrBytes[32]; bool cancelWrap = false; uint8_t outlineColor = 0; uint8_t outlineNeighbors = 0; bool underline = false; uint8_t printMode = _ph_mem->hwState.printAttributes; if ((printMode & 0x1) == 0) { printMode = 0; } if ((printMode & PRINT_MODE_CUSTOM_FONT)) { charWidth = _ph_mem->data[0x5600]; charHeight = _ph_mem->data[0x5602]; } uint8_t* drawPal = _ph_mem->drawState.drawPaletteMap; int framesBetweenChars = 0; int framesToPause = 0; int rhsWrap = -1; for (size_t n = 0; n < length; n++) { uint8_t ch = str[n]; framesToPause = framesBetweenChars; if (ch == 0) { // null stop printing _ph_mem->drawState.text_x=x; _ph_mem->drawState.text_y=y; return x; } else if (ch == 1) { // "\*{p0}" repeat the next character p0 times uint8_t timesChr = str[++n]; int times = p0CharToNum(timesChr); ch = str[++n]; for(int i = 0; i < times; i++) { //TODO: combine with other draw character call - fix missing bg? lineheight? x += charWidth + _ph_graphics->drawCharacter( ch, x, y, drawPal[fgColor & 0x0f], bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f], printMode, forceCharWidth, forceCharHeight); } } else if (ch == 2) { // "\#{p0}" draw text on a solid background color uint8_t bgColChar = str[++n]; bgColor = p0CharToNum(bgColChar); printMode |= PRINT_MODE_ON; printMode |= PRINT_MODE_SOLID_BG; } else if (ch == 3) { // "\-{p0}" move text cursor horizontally by 16-p0 pixels uint8_t pixelCountChar = str[++n]; int deltaX = p0CharToNum(pixelCountChar) - 16; x += deltaX; //update memory cursor state? } else if (ch == 4) { // "\|{p0}" move text cursor vertically by 16-p0 pixels uint8_t pixelCountChar = str[++n]; int deltaY = p0CharToNum(pixelCountChar) - 16; y += deltaY; //update memory cursor state? } else if (ch == 5) { // "\+{p0}" move text cursor vertically by 16-p0 pixels uint8_t pixelCountXChar = str[++n]; uint8_t pixelCountYChar = str[++n]; int deltaX = p0CharToNum(pixelCountXChar) - 16; int deltaY = p0CharToNum(pixelCountYChar) - 16; x += deltaX; y += deltaY; //update memory cursor state? } else if (ch == 6) { // "\^" special command uint8_t commandChar = str[++n]; // \^1 through \^9 skips a number of frames before continuing printing if (commandChar > 48 && commandChar < 58){ //pause for x num frames int frameCount = pow2(p0CharToNum(commandChar) - 1); while (frameCount > 0){ //TODO: yield here? not sure how to handle this //_ph_vm->vm_flip(); frameCount--; } } // \^d P0 causes subsequent printing to pause P0 frames between each character, else if (commandChar == 'd') { uint8_t framesChar = str[++n]; framesBetweenChars = p0CharToNum(framesChar); } else if (commandChar == 'c'){ uint8_t colChar = str[++n]; uint8_t col = p0CharToNum(colChar); _ph_graphics->cls(col); } else if (commandChar == 'g'){ x = homeX; y = homeY; } else if (commandChar == 'h'){ homeX = x; homeY = y; } else if (commandChar == 'j'){ uint8_t xChar = str[++n]; uint8_t yChar = str[++n]; x = p0CharToNum(xChar) * 4; y = p0CharToNum(yChar) * 4; } else if (commandChar == 'r'){ uint8_t rhsWrapChar = str[++n]; rhsWrap = p0CharToNum(rhsWrapChar) * 4; } else if (commandChar == 's'){ uint8_t tswChar = str[++n]; tabStopWidth = p0CharToNum(tswChar); } else if (commandChar == 'x'){ uint8_t charWidthChar = str[++n]; forceCharWidth = p0CharToNum(charWidthChar); charWidth = forceCharWidth; } else if (commandChar == 'y'){ //this behaves in a way I wouldn't expect in pico 8- it seems to apply to the next print statment //but not if there is a \n in the string. Not sure if this is a bug or not uint8_t charHeightChar = str[++n]; forceCharHeight = p0CharToNum(charHeightChar); charHeight = forceCharHeight; } else if (commandChar == 'w'){ printMode |= PRINT_MODE_ON; printMode |= PRINT_MODE_WIDE; charWidth = 8; } else if (commandChar == 't'){ printMode |= PRINT_MODE_ON; printMode |= PRINT_MODE_TALL; charHeight = 12; } else if (commandChar == '='){ printMode |= PRINT_MODE_ON; printMode |= PRINT_MODE_STRIPEY; } else if (commandChar == 'p'){ printMode |= PRINT_MODE_ON; printMode |= PRINT_MODE_WIDE; printMode |= PRINT_MODE_TALL; printMode |= PRINT_MODE_STRIPEY; charWidth = 8; charHeight = 12; } else if (commandChar == 'i'){ printMode |= PRINT_MODE_ON; printMode |= PRINT_MODE_INVERTED; } else if (commandChar == 'b'){ printMode |= PRINT_MODE_ON; printMode |= PRINT_MODE_PADDING; } else if (commandChar == '#'){ printMode |= PRINT_MODE_ON; printMode |= PRINT_MODE_SOLID_BG; } else if (commandChar == ':' || commandChar == ';' || commandChar == '.' || commandChar == ','){ //the docs say ';' and ',' should respect padding, but in my testing //it looks like they behave exactly the same... /shrug charHeight = forceCharHeight > 0 ? forceCharHeight : 8; if (commandChar == ':' || commandChar == ';') { std::string hexStr = str.substr(n+1, 16); n+=16; hexStrToBytes(hexStr, charBytes); } else { std::string binStr = str.substr(n+1, 8); n+=8; for(size_t i = 0; i < 8; i++) { charBytes[i] = binStr[i]; } } auto values = _ph_graphics->drawCharacterFromBytes( charBytes, x, y, drawPal[fgColor & 0x0f], bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f], printMode, 8, charHeight); x += 8 + get<0>(values); charHeight = charHeight + get<1>(values); lineHeight = charHeight > lineHeight ? charHeight : lineHeight; } else if (commandChar == '-'){ uint8_t turnOffModeChar = str[++n]; if (printMode) { if (turnOffModeChar == 'w') { printMode &= ~(PRINT_MODE_WIDE); charWidth = 4; } else if (turnOffModeChar == 't') { printMode &= ~(PRINT_MODE_TALL); charHeight = 6; } else if (turnOffModeChar == '=') { printMode &= ~(PRINT_MODE_STRIPEY); } else if (turnOffModeChar == 'p') { printMode &= ~(PRINT_MODE_WIDE); printMode &= ~(PRINT_MODE_TALL); printMode &= ~(PRINT_MODE_STRIPEY); charWidth = 4; charHeight = 6; } else if (turnOffModeChar == 'i') { printMode &= ~(PRINT_MODE_INVERTED); } else if (turnOffModeChar == 'b') { printMode &= ~(PRINT_MODE_PADDING); } else if (turnOffModeChar == '#') { printMode &= ~(PRINT_MODE_SOLID_BG); } } } else if (commandChar == '!'){ //no wraping after this print call (may be pico 8 bug?) cancelWrap = true; std::string addrHexStr = str.substr(n+1, 4); n+=4; int addr = (int)strtol(addrHexStr.c_str(), NULL, 16); int size = length - n; std::string binStr = str.substr(n + 1, size); n+=size; for(size_t i = 0; i < size; i++) { _ph_mem->data[addr + i] = binStr[i]; } } else if (commandChar == '@'){ std::string addrHexStr = str.substr(n+1, 4); n+=4; int addr = (int)strtol(addrHexStr.c_str(), NULL, 16); std::string sizeHexStr = str.substr(n+1, 4); n+=4; int size = (int)strtol(sizeHexStr.c_str(), NULL, 16); std::string binStr = str.substr(n + 1, size); n+= size; for(size_t i = 0; i < size; i++) { _ph_mem->data[addr + i] = binStr[i]; } } else if (commandChar == 'o'){ //Pico-8 docs for outline: //The first character after the command "\^o" is the colour, and the following two // characters are the neighbours bitfield in hexadecimal. For example, to draw a pixel up // to the left of each foreground pixel, the value 0x01 can be used: // > ?"\^o801hey" uint8_t outlineColorChar = str[++n]; outlineColor = p0CharToNum(outlineColorChar); std::string neighborsHexStr = str.substr(n+1, 2); n+=2; outlineNeighbors = (uint8_t)strtol(neighborsHexStr.c_str(), NULL, 16); } else if (commandChar == 'u'){ underline = true; } } else if (ch == 7) { // "\a" audio command uint8_t nextChar = str[++n]; int audioStrIdx = 0; while (nextChar != 32 && n < length) { audioStrBytes[audioStrIdx++] = nextChar; nextChar = str[++n]; } //todo: generate audio stuff and play } else if (ch == 11) { // "\v" decorate previous character uint8_t offsetChar = str[++n]; uint8_t toPrint = str[++n]; int offset = p0CharToNum(offsetChar); int xOffset = (offset%4)-2; int yOffset = (offset/4)-8; _ph_graphics->drawCharacter( toPrint, prevX + xOffset, prevY + yOffset, drawPal[fgColor & 0x0f], bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f], printMode, forceCharWidth, forceCharHeight); } else if (ch == 12) { //"\f{p0}" draw text with this foreground color uint8_t fgColChar = str[++n]; fgColor = p0CharToNum(fgColChar); _ph_graphics->color(fgColor); } else if (ch == 14) { //"\014" Turn on custom font stored at 0x5600 printMode |= PRINT_MODE_CUSTOM_FONT; charWidth = _ph_mem->data[0x5600]; charHeight = _ph_mem->data[0x5602]; } else if (ch == 15) { //"\015" Turn off custom font stored at 0x5600 printMode &= ~(PRINT_MODE_CUSTOM_FONT); charWidth = 4; charHeight = 6; } else if (ch == '\n') { x = homeX; lineHeight = lineHeight > 0 ? lineHeight : 6; y += lineHeight; lineHeight = 0; } else if (ch == '\t') { while (x % (tabStopWidth*4) > 0) { x++; } } else if (ch == '\b') { x -= charWidth; } else if (ch == '\r') { x = homeX; } else if (ch >= 0x10) { lineHeight = charHeight > lineHeight ? charHeight : lineHeight; if (printMode & PRINT_MODE_SOLID_BG) { uint8_t prevPenColor = _ph_mem->drawState.color; _ph_graphics->rectfill(x-1, y-1, x + charWidth-1, y + lineHeight-1, bgColor); _ph_mem->drawState.color = prevPenColor; } prevX = x; prevY = y; if (outlineColor != 0 && outlineNeighbors != 0) { /* 0x01 0x02 0x04 0x08 -- 0x10 0x20 0x40 0x80 */ int xOffsets[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; int yOffsets[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; for (int i = 0; i < 8; i++) { int xOffset = xOffsets[i]; int yOffset = yOffsets[i]; if (xOffset == 0 && yOffset == 0) { continue; } if (outlineNeighbors & (1 << i)) { _ph_graphics->drawCharacter( ch, x + xOffset, y + yOffset, drawPal[outlineColor & 0x0f], bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f], printMode, forceCharWidth, forceCharHeight); } } } if (underline) { _ph_graphics->rectfill(x-1, y + lineHeight, x + charWidth - 1, y + lineHeight, fgColor); } int extraWidth = _ph_graphics->drawCharacter( ch, x, y, drawPal[fgColor & 0x0f], bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f], printMode, forceCharWidth, forceCharHeight); // Apply per-character width adjustment if flag 0x1 is set at 0x5605 int perCharWidthAdj = 0; if ((printMode & PRINT_MODE_CUSTOM_FONT) && (_ph_mem->data[0x5605] & 0x01)) { int nibbleIdx = ch - 16; if (nibbleIdx >= 0 && nibbleIdx < 240) { uint8_t adjByte = _ph_mem->data[0x5608 + nibbleIdx/2]; uint8_t adjNibble = (nibbleIdx % 2 == 0) ? (adjByte & 0x0f) : (adjByte >> 4); perCharWidthAdj = (adjNibble & 0x07); if (perCharWidthAdj >= 4) perCharWidthAdj = perCharWidthAdj - 8; // convert 4,5,6,7 to -4,-3,-2,-1 } } x += charWidth + extraWidth + perCharWidthAdj; while (framesToPause > 0){ //TODO: yield here? not sure how to handle this //_ph_vm->vm_flip(); framesToPause--; } } //soft wrap if enabled if (rhsWrap > 0 && x >= rhsWrap) { x = homeX; lineHeight = lineHeight > 0 ? lineHeight : 6; y += lineHeight; lineHeight = 0; } } lineHeight = lineHeight > 0 ? lineHeight : cancelWrap ? 0 : 6; //todo: auto scrolling _ph_mem->drawState.text_y = y + lineHeight; return x; }