Cleaned up VT100 font support a bit
This commit is contained in:
parent
21fc2e0639
commit
96d129e469
1 changed files with 95 additions and 66 deletions
|
|
@ -255,21 +255,21 @@ Video.FORMATS = {
|
||||||
|
|
||||||
|
|
||||||
Video.VT100 = {
|
Video.VT100 = {
|
||||||
|
/*
|
||||||
|
* The following font IDs are nothing more than all the possible LINEATTR values masked with FONTMASK;
|
||||||
|
* it's also worth noting that double-high implies double-wide; the VT100 doesn't support double-high single-wide.
|
||||||
|
*/
|
||||||
FONT: {
|
FONT: {
|
||||||
NORML: 0,
|
|
||||||
DWIDE: 1,
|
|
||||||
DHIGH: 2
|
|
||||||
},
|
|
||||||
LINETERM: 0x7F,
|
|
||||||
LINEATTR: {
|
|
||||||
MASK: 0xE0,
|
|
||||||
SCROLL: 0x80,
|
|
||||||
ADDRMASK: 0x1F,
|
|
||||||
FONTMASK: 0x60,
|
|
||||||
NORML: 0x60,
|
NORML: 0x60,
|
||||||
DWIDE: 0x40,
|
DWIDE: 0x40,
|
||||||
DHIGH_TOP: 0x20,
|
DHIGH_TOP: 0x20,
|
||||||
DHIGH_BOT: 0x00
|
DHIGH_BOT: 0x00
|
||||||
|
},
|
||||||
|
LINETERM: 0x7F,
|
||||||
|
LINEATTR: {
|
||||||
|
ADDRMASK: 0x1F,
|
||||||
|
FONTMASK: 0x60,
|
||||||
|
SCROLL: 0x80
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -291,7 +291,14 @@ Video.prototype.initBuffers = function()
|
||||||
cyBuffer = this.cxBuffer;
|
cyBuffer = this.cxBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.imageBuffer = this.contextScreen.createImageData(cxBuffer, cyBuffer);
|
/*
|
||||||
|
* imageBuffer is only used for graphics modes. For text modes, we create a canvas
|
||||||
|
* for each font and draw characters by drawing from the font canvas to the target canvas.
|
||||||
|
*/
|
||||||
|
if (this.cxCell == 1) {
|
||||||
|
this.imageBuffer = this.contextScreen.createImageData(cxBuffer, cyBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
this.canvasBuffer = document.createElement("canvas");
|
this.canvasBuffer = document.createElement("canvas");
|
||||||
this.canvasBuffer.width = cxBuffer;
|
this.canvasBuffer.width = cxBuffer;
|
||||||
this.canvasBuffer.height = cyBuffer;
|
this.canvasBuffer.height = cyBuffer;
|
||||||
|
|
@ -299,12 +306,12 @@ Video.prototype.initBuffers = function()
|
||||||
|
|
||||||
this.initColors();
|
this.initColors();
|
||||||
|
|
||||||
if (this.cxCell > 1) {
|
if (this.nFormat == Video.FORMAT.VT100) {
|
||||||
this.aFonts = {};
|
this.aFonts = {};
|
||||||
this.abLineBuffer = new Array(132);
|
this.abLineBuffer = new Array(132); // allocated to the maximum line length supported (132 columns)
|
||||||
this.aFonts[Video.VT100.FONT.NORML] = this.createFontVariation(this.cxCell, this.cyCell);
|
this.aFonts[Video.VT100.FONT.NORML] = this.createFontVariation(this.cxCell, this.cyCell);
|
||||||
this.aFonts[Video.VT100.FONT.DWIDE] = this.createFontVariation(this.cxCell*2, this.cyCell);
|
this.aFonts[Video.VT100.FONT.DWIDE] = this.createFontVariation(this.cxCell*2, this.cyCell);
|
||||||
this.aFonts[Video.VT100.FONT.DHIGH] = this.createFontVariation(this.cxCell*2, this.cyCell*2);
|
this.aFonts[Video.VT100.FONT.DHIGH_TOP] = this.aFonts[Video.VT100.FONT.DHIGH_BOT] = this.createFontVariation(this.cxCell*2, this.cyCell*2);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -444,7 +451,6 @@ Video.prototype.createFontVariation = function(cxCell, cyCell, rgbOn, fUnderline
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* On a VT100, cxCell,cyCell is initially 10,10, but may change to 9,10 for 132-column mode.
|
* On a VT100, cxCell,cyCell is initially 10,10, but may change to 9,10 for 132-column mode.
|
||||||
* However, regardless of mode, cxCellDefault,cyCellDefault retain the initial values of 10,10.
|
|
||||||
*/
|
*/
|
||||||
this.assert(cxCell == this.cxCell || cxCell == this.cxCell*2);
|
this.assert(cxCell == this.cxCell || cxCell == this.cxCell*2);
|
||||||
this.assert(cyCell == this.cyCell || cyCell == this.cyCell*2);
|
this.assert(cyCell == this.cyCell || cyCell == this.cyCell*2);
|
||||||
|
|
@ -469,10 +475,16 @@ Video.prototype.createFontVariation = function(cxCell, cyCell, rgbOn, fUnderline
|
||||||
for (var iChar = 0; iChar < nChars; iChar++) {
|
for (var iChar = 0; iChar < nChars; iChar++) {
|
||||||
for (var y = 0, yDst = y; y < this.cyCell; y++) {
|
for (var y = 0, yDst = y; y < this.cyCell; y++) {
|
||||||
var offFontData = iChar * nFontBytesPerChar + ((nFontByteOffset + y) & (nFontBytesPerChar - 1));
|
var offFontData = iChar * nFontBytesPerChar + ((nFontByteOffset + y) & (nFontBytesPerChar - 1));
|
||||||
var b = this.abFontData[offFontData];
|
var bits = this.abFontData[offFontData];
|
||||||
for (var nRows = 0; nRows < (cyCell / this.cyCell); nRows++) {
|
for (var nRows = 0; nRows < (cyCell / this.cyCell); nRows++) {
|
||||||
for (var x = 0, xDst = x; x < this.cxCell; x++) {
|
for (var x = 0, xDst = x; x < this.cxCell; x++) {
|
||||||
var bit = b & (0x80 >> x);
|
/*
|
||||||
|
* While x goes from 0 to cxCell-1, obviously we will run out of bits after x is 7;
|
||||||
|
* since the final bit must be replicated all the way to the right edge of the cell
|
||||||
|
* (so that line-drawing characters seamlessly connect), we ensure that the effective
|
||||||
|
* shift count remains stuck at 7.
|
||||||
|
*/
|
||||||
|
var bit = bits & (0x80 >> (x > 7? 7 : x));
|
||||||
for (var nCols = 0; nCols < (cxCell / this.cxCell); nCols++) {
|
for (var nCols = 0; nCols < (cxCell / this.cxCell); nCols++) {
|
||||||
this.setPixel(imageChar, xDst, yDst, bit? 1 : 0);
|
this.setPixel(imageChar, xDst, yDst, bit? 1 : 0);
|
||||||
xDst++;
|
xDst++;
|
||||||
|
|
@ -675,6 +687,7 @@ Video.prototype.initColors = function()
|
||||||
this.aRGB[1] = rgbWhite;
|
this.aRGB[1] = rgbWhite;
|
||||||
if (this.nFormat == Video.FORMAT.SI1978) {
|
if (this.nFormat == Video.FORMAT.SI1978) {
|
||||||
var rgbGreen = [0x00, 0xff, 0x00, 0xff];
|
var rgbGreen = [0x00, 0xff, 0x00, 0xff];
|
||||||
|
//noinspection UnnecessaryLocalVariableJS
|
||||||
var rgbYellow = [0xff, 0xff, 0x00, 0xff];
|
var rgbYellow = [0xff, 0xff, 0x00, 0xff];
|
||||||
this.aRGB[this.nColors + Video.COLORS.OVERLAY_TOP] = rgbYellow;
|
this.aRGB[this.nColors + Video.COLORS.OVERLAY_TOP] = rgbYellow;
|
||||||
this.aRGB[this.nColors + Video.COLORS.OVERLAY_BOTTOM] = rgbGreen;
|
this.aRGB[this.nColors + Video.COLORS.OVERLAY_BOTTOM] = rgbGreen;
|
||||||
|
|
@ -682,21 +695,21 @@ Video.prototype.initColors = function()
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* setPixel(imageBuffer, x, y, bPixel)
|
* setPixel(image, x, y, bPixel)
|
||||||
*
|
*
|
||||||
* @this {Video}
|
* @this {Video}
|
||||||
* @param {Object} imageBuffer
|
* @param {Object} image
|
||||||
* @param {number} x
|
* @param {number} x
|
||||||
* @param {number} y
|
* @param {number} y
|
||||||
* @param {number} bPixel (ie, an index into aRGB)
|
* @param {number} bPixel (ie, an index into aRGB)
|
||||||
*/
|
*/
|
||||||
Video.prototype.setPixel = function(imageBuffer, x, y, bPixel)
|
Video.prototype.setPixel = function(image, x, y, bPixel)
|
||||||
{
|
{
|
||||||
var index;
|
var index;
|
||||||
if (!this.rotateBuffer) {
|
if (!this.rotateBuffer) {
|
||||||
index = (x + y * imageBuffer.width);
|
index = (x + y * image.width);
|
||||||
} else {
|
} else {
|
||||||
index = (imageBuffer.height - x - 1) * imageBuffer.width + y;
|
index = (image.height - x - 1) * image.width + y;
|
||||||
}
|
}
|
||||||
if (bPixel && this.nFormat == Video.FORMAT.SI1978) {
|
if (bPixel && this.nFormat == Video.FORMAT.SI1978) {
|
||||||
if (x >= 208 && x < 236) {
|
if (x >= 208 && x < 236) {
|
||||||
|
|
@ -708,50 +721,75 @@ Video.prototype.setPixel = function(imageBuffer, x, y, bPixel)
|
||||||
}
|
}
|
||||||
var rgb = this.aRGB[bPixel];
|
var rgb = this.aRGB[bPixel];
|
||||||
index *= rgb.length;
|
index *= rgb.length;
|
||||||
imageBuffer.data[index] = rgb[0];
|
image.data[index] = rgb[0];
|
||||||
imageBuffer.data[index+1] = rgb[1];
|
image.data[index+1] = rgb[1];
|
||||||
imageBuffer.data[index+2] = rgb[2];
|
image.data[index+2] = rgb[2];
|
||||||
imageBuffer.data[index+3] = rgb[3];
|
image.data[index+3] = rgb[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* updateChar(font, col, row, data, context)
|
* updateChar(idFont, col, row, data, context)
|
||||||
*
|
*
|
||||||
* Updates a particular character cell (row,col) in the associated window.
|
* Updates a particular character cell (row,col) in the associated window.
|
||||||
*
|
*
|
||||||
* @this {Video}
|
* @this {Video}
|
||||||
* @param {Object} font
|
* @param {number} idFont
|
||||||
* @param {number} col
|
* @param {number} col
|
||||||
* @param {number} row
|
* @param {number} row
|
||||||
* @param {number} data
|
* @param {number} data
|
||||||
* @param {Object} [context]
|
* @param {Object} [context]
|
||||||
*/
|
*/
|
||||||
Video.prototype.updateChar = function(font, col, row, data, context)
|
Video.prototype.updateChar = function(idFont, col, row, data, context)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* The caller MUST promise this.nFont is defined, and that the font in this.aFonts[this.nFont] has been loaded.
|
|
||||||
*/
|
|
||||||
var xDst, yDst;
|
|
||||||
var bChar = data & 0x7f;
|
var bChar = data & 0x7f;
|
||||||
|
var font = this.aFonts[idFont];
|
||||||
|
|
||||||
if (context) {
|
|
||||||
xDst = col * font.cxCell;
|
|
||||||
yDst = row * font.cyCell;
|
|
||||||
} else {
|
|
||||||
xDst = col * this.cxScreenCell + this.xScreenOffset;
|
|
||||||
yDst = row * this.cyScreenCell + this.yScreenOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* (bChar & 0xf) is the equivalent of (bChar % 16), and (bChar >> 4) is the equivalent of Math.floor(bChar / 16)
|
|
||||||
*/
|
|
||||||
var xSrc = (bChar & 0xf) * font.cxCell;
|
var xSrc = (bChar & 0xf) * font.cxCell;
|
||||||
var ySrc = (bChar >> 4) * font.cyCell;
|
var ySrc = (bChar >> 4) * font.cyCell;
|
||||||
|
|
||||||
|
var xDst, yDst, cxDst, cyDst;
|
||||||
|
|
||||||
|
var cxSrc = font.cxCell;
|
||||||
|
var cySrc = font.cyCell;
|
||||||
|
|
||||||
if (context) {
|
if (context) {
|
||||||
context.drawImage(font.canvas, xSrc, ySrc, font.cxCell, font.cyCell, xDst, yDst, font.cxCell, font.cyCell);
|
xDst = col * this.cxCell;
|
||||||
|
yDst = row * this.cyCell;
|
||||||
|
cxDst = this.cxCell;
|
||||||
|
cyDst = this.cyCell;
|
||||||
} else {
|
} else {
|
||||||
this.contextScreen.drawImage(font.canvas, xSrc, ySrc, font.cxCell, font.cyCell, xDst, yDst, this.cxScreenCell, this.cyScreenCell);
|
xDst = col * this.cxScreenCell;
|
||||||
|
yDst = row * this.cyScreenCell;
|
||||||
|
cxDst = this.cxScreenCell;
|
||||||
|
cyDst = this.cyScreenCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If font.cxCell > this.cxCell, then we assume the caller wants to draw a double-wide character,
|
||||||
|
* so we will double xDst and cxDst.
|
||||||
|
*/
|
||||||
|
if (font.cxCell > this.cxCell) {
|
||||||
|
xDst *= 2;
|
||||||
|
cxDst *= 2;
|
||||||
|
this.assert(font.cxCell == this.cxCell * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If font.cyCell > this.cyCell, then we rely on idFont to indicate whether the top half or bottom half
|
||||||
|
* of the character should be drawn.
|
||||||
|
*/
|
||||||
|
if (font.cyCell > this.cyCell) {
|
||||||
|
if (idFont == Video.VT100.FONT.DHIGH_BOT) ySrc += this.cyCell;
|
||||||
|
cySrc = this.cyCell;
|
||||||
|
this.assert(font.cyCell == this.cyCell * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
context.drawImage(font.canvas, xSrc, ySrc, cxSrc, cySrc, xDst, yDst, cxDst, cyDst);
|
||||||
|
} else {
|
||||||
|
xDst += this.xScreenOffset;
|
||||||
|
yDst += this.yScreenOffset;
|
||||||
|
this.contextScreen.drawImage(font.canvas, xSrc, ySrc, cxSrc, cySrc, xDst, yDst, cxDst, cyDst);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -840,7 +878,7 @@ Video.prototype.updateScreenText = function()
|
||||||
*/
|
*/
|
||||||
Video.prototype.updateScreenVT100 = function()
|
Video.prototype.updateScreenVT100 = function()
|
||||||
{
|
{
|
||||||
var addrNext = this.addrBuffer, attrNext = -1;
|
var addrNext = this.addrBuffer, fontNext = -1;
|
||||||
|
|
||||||
for (var iRow = 0; iRow < this.nRowsBuffer+1; iRow++) {
|
for (var iRow = 0; iRow < this.nRowsBuffer+1; iRow++) {
|
||||||
/*
|
/*
|
||||||
|
|
@ -848,12 +886,12 @@ Video.prototype.updateScreenVT100 = function()
|
||||||
*/
|
*/
|
||||||
var cbCols = 0;
|
var cbCols = 0;
|
||||||
var addr = addrNext;
|
var addr = addrNext;
|
||||||
var attr = attrNext;
|
var font = fontNext;
|
||||||
while (cbCols < this.abLineBuffer.length) {
|
while (cbCols < this.abLineBuffer.length) {
|
||||||
var data = this.bus.getByteDirect(addr++);
|
var data = this.bus.getByteDirect(addr++);
|
||||||
if ((data & Video.VT100.LINETERM) == Video.VT100.LINETERM) {
|
if ((data & Video.VT100.LINETERM) == Video.VT100.LINETERM) {
|
||||||
var b = this.bus.getByteDirect(addr++);
|
var b = this.bus.getByteDirect(addr++);
|
||||||
attrNext = b & Video.VT100.LINEATTR.FONTMASK;
|
fontNext = b & Video.VT100.LINEATTR.FONTMASK;
|
||||||
addrNext = this.addrBuffer | ((b & Video.VT100.LINEATTR.ADDRMASK) << 8) | this.bus.getByteDirect(addr);
|
addrNext = this.addrBuffer | ((b & Video.VT100.LINEATTR.ADDRMASK) << 8) | this.bus.getByteDirect(addr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -862,28 +900,14 @@ Video.prototype.updateScreenVT100 = function()
|
||||||
/*
|
/*
|
||||||
* Display the line buffer
|
* Display the line buffer
|
||||||
*/
|
*/
|
||||||
if (attr >= 0) {
|
if (font >= 0) {
|
||||||
var idFont, cyClip = 0;
|
for (var iCol = 0; iCol < cbCols; iCol++) {
|
||||||
switch(attr) {
|
/*
|
||||||
case Video.VT100.LINEATTR.NORML:
|
* TODO: Until support is added for a cell cache, we update the screen buffer directly.
|
||||||
idFont = Video.VT100.FONT.NORML;
|
*
|
||||||
break;
|
* this.updateChar(font, iCol, iRow-1, this.abLineBuffer[iCol], this.contextBuffer);
|
||||||
case Video.VT100.LINEATTR.DWIDE:
|
*/
|
||||||
idFont = Video.VT100.FONT.DWIDE;
|
this.updateChar(font, iCol, iRow-1, this.abLineBuffer[iCol]);
|
||||||
break;
|
|
||||||
case Video.VT100.LINEATTR.DHIGH_TOP:
|
|
||||||
idFont = Video.VT100.FONT.DHIGH;
|
|
||||||
break;
|
|
||||||
case Video.VT100.LINEATTR.DHIGH_BOT:
|
|
||||||
idFont = Video.VT100.FONT.DHIGH;
|
|
||||||
cyClip = this.cyCell;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var font = this.aFonts[idFont];
|
|
||||||
if (font) {
|
|
||||||
for (var iCol = 0; iCol < cbCols; iCol++) {
|
|
||||||
this.updateChar(font, iCol, iRow-1, this.abLineBuffer[iCol], this.contextBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -966,6 +990,11 @@ Video.prototype.updateScreenGraphics = function()
|
||||||
cyDirty = cxDirtyOrig;
|
cyDirty = cxDirtyOrig;
|
||||||
}
|
}
|
||||||
this.contextBuffer.putImageData(this.imageBuffer, 0, 0, xDirty, yDirty, cxDirty, cyDirty);
|
this.contextBuffer.putImageData(this.imageBuffer, 0, 0, xDirty, yDirty, cxDirty, cyDirty);
|
||||||
|
/*
|
||||||
|
* As noted originally in /modules/pcx86/lib/video.js, I would prefer to draw only the dirty portion of
|
||||||
|
* canvasBuffer, but there usually isn't a 1-1 pixel mapping between canvasBuffer and contextScreen, so
|
||||||
|
* if we draw interior rectangles, we can end up with subpixel artifacts along the edges of those rectangles.
|
||||||
|
*/
|
||||||
this.contextScreen.drawImage(this.canvasBuffer, 0, 0, this.canvasBuffer.width, this.canvasBuffer.height, 0, 0, this.cxScreen, this.cyScreen);
|
this.contextScreen.drawImage(this.canvasBuffer, 0, 0, this.canvasBuffer.width, this.canvasBuffer.height, 0, 0, this.cxScreen, this.cyScreen);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue