* Lots of work with fonts: Support mappings of various cterm/SyncTERM fonts.

* Load font info from SAUCE
* Better work with defaults & theme values
This commit is contained in:
Bryan Ashby 2015-04-16 22:29:53 -06:00
parent 586f3d60b3
commit 5faa11664b
15 changed files with 233 additions and 39 deletions

View File

@ -14,6 +14,8 @@ var assert = require('assert');
var binary = require('binary');
var miscUtil = require('./misc_util.js');
var _ = require('lodash');
exports.getFGColorValue = getFGColorValue;
exports.getBGColorValue = getBGColorValue;
exports.sgr = sgr;
@ -22,7 +24,7 @@ exports.resetScreen = resetScreen;
exports.normal = normal;
exports.goHome = goHome;
exports.disableVT100LineWrapping = disableVT100LineWrapping;
exports.setSyncTermFont = setSyncTermFont;
exports.setFont = setFont;
exports.fromPipeCode = fromPipeCode;
@ -115,6 +117,95 @@ function getBGColorValue(name) {
// See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt
// :TODO: document
// :TODO: Create mappings for aliases... maybe make this a map to values instead
var FONT_MAP = {
// Codepage 437 English
'cp437' : 0,
'ibmpc' : 0,
'ibm_pc' : 0,
'ibm_vga' : 0,
'pc' : 0,
'cp437_art' : 0,
'ibmpcart' : 0,
'ibmpc_art' : 0,
'ibm_pc_art' : 0,
'msdos_art' : 0,
'msdosart' : 0,
'pc_art' : 0,
'pcart' : 0,
// Codepage 1251 Cyrillic, (swiss)
'cp1251-swiss' : 1,
// Russian koi8-r
'koi8_r' : 2,
'koi8-r' : 2,
'koi8r' : 2,
// ISO-8859-2 Central European
'iso8859_2' : 3,
'iso8859-2' : 3,
// ISO-8859-4 Baltic wide (VGA 9bit mapped)
'iso8859_4-baltic9b' : 4,
// Codepage 866 (c) Russian
'cp866-c' : 5,
'iso8859_9' : 6,
'haik8' : 7,
'iso8859_8' : 8,
'koi8_u' : 9,
'iso8859_15-thin' : 10,
'iso8859_4' : 11,
'koi8_r_b' : 12,
'iso8859_4-baltic-wide' : 13,
'iso8859_5' : 14,
'ARMSCII_8' : 15,
'iso8859_15' : 16,
'cp850' : 17,
'cp850-thin' : 18,
'cp885-thin' : 19,
'cp1251' : 20,
'iso8859_7' : 21,
'koi8-r_c' : 22,
'iso8859_4-baltic' : 23,
'iso8859_1' : 24,
'cp866' : 25,
'cp437-thin' : 26,
'cp866-b' : 27,
'cp885' : 28,
'cp866_u' : 29,
'iso8859_1-thin' : 30,
'cp1131' : 31,
'c64_upper' : 32,
'c64_lower' : 33,
'c128_upper' : 34,
'c128_lower' : 35,
'atari' : 36,
'atarist' : 36,
'pot_noodle' : 37,
'p0tnoodle' : 37,
'mo_soul' : 38,
'mosoul' : 38,
'mO\'sOul' : 38,
'microknight_plus' : 39,
'topaz_plus' : 40,
'topazplus' : 40,
'amiga_topaz_2+' : 40,
'topaz2plus' : 40,
'microknight' : 41,
'topaz' : 42,
};
var SYNC_TERM_FONTS = [
'cp437',
'cp1251',
@ -155,8 +246,10 @@ var SYNC_TERM_FONTS = [
'atari',
'pot_noodle',
'mo_soul',
'microknight',
'topaz'
'microknight_plus',
'topaz_plus',
'microknight',
'topaz',
];
// Create methods such as up(), nextLine(),...
@ -242,7 +335,12 @@ function disableVT100LineWrapping() {
return ESC_CSI + '7l';
}
function setSyncTermFont(name, fontPage) {
//
// See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt
//
// :TODO: allow full spec here.
/*
function setFont(name, fontPage) {
fontPage = miscUtil.valueWithDefault(fontPage, 0);
assert(fontPage === 0 || fontPage === 1); // see spec
@ -253,6 +351,22 @@ function setSyncTermFont(name, fontPage) {
}
return '';
}
*/
function setFont(name, fontPage) {
name = name.toLowerCase().replace(/ /g, '_'); // conform to map
var p1 = miscUtil.valueWithDefault(fontPage, 0);
assert(p1 >= 0 && p1 <= 3);
var p2 = FONT_MAP[name];
if(_.isNumber(p2)) {
return ESC_CSI + p1 + ';' + p2 + ' D';
}
return '';
}
// Also add:
// * fromRenegade(): |<0-23>

View File

@ -189,6 +189,12 @@ function parseCharacterSAUCE(sauce) {
return result;
}
function getFontNameFromSAUCE(sauce) {
if(sauce.Character) {
return sauce.Character.fontName;
}
}
function sliceAtEOF(data, eofMarker) {
var eof = data.length;
// :TODO: max scan back or other beter way of doing this?!
@ -213,7 +219,7 @@ function getArtFromPath(path, options, cb) {
//
var ext = paths.extname(path).toLowerCase();
var encoding = options.encodedAs || defaultEncodingFromExtension(ext);
// :TODO: how are BOM's currently handled if present? Are they removed? Do we need to?
function sliceOfData() {
@ -248,12 +254,14 @@ function getArtFromPath(path, options, cb) {
// the information provided by SAUCE, use that.
//
if(!options.encodedAs) {
/*
if(sauce.Character && sauce.Character.fontName) {
var enc = SAUCE_FONT_TO_ENCODING_HINT[sauce.Character.fontName];
if(enc) {
encoding = enc;
}
}
*/
}
cb(null, getResult(sauce));
}
@ -469,6 +477,26 @@ function display(options, cb) {
}
});
// :TODO: If options.font, set the font via ANSI
// ...this should come from sauce, be passed in, or defaulted
var ansiFont = '';
if(options.font) {
// :TODO: how to set to ignore SAUCE?
ansiFont = ansi.setFont(options.font);
} else if(options.sauce) {
var fontName = getFontNameFromSAUCE(options.sauce);
if(fontName) {
ansiFont = ansi.setFont(fontName);
}
}
if(ansiFont.length > 1) {
options.client.term.write(ansiFont);
}
if(iceColors) {
options.client.term.write(ansi.blinkToBrightIntensity());
}

View File

@ -29,6 +29,6 @@ ButtonView.prototype.onKeyPress = function(key, isSpecial) {
}
};
ButtonView.prototype.getViewData = function() {
ButtonView.prototype.getData = function() {
return null;
};

View File

@ -79,10 +79,11 @@ function Client(input, output) {
var self = this;
this.input = input;
this.output = output;
this.term = new term.ClientTerminal(this.output);
this.user = new user.User();
this.input = input;
this.output = output;
this.term = new term.ClientTerminal(this.output);
this.user = new user.User();
this.currentThemeInfo = { name : 'N/A', description : 'None' };
//
// Peek at |data| and emit for any specialized handling

View File

@ -5,6 +5,8 @@ var fs = require('fs');
var paths = require('path');
var miscUtil = require('./misc_util.js');
// :TODO: it would be nice to allow for defaults here & .json file only overrides -- e.g. merge the two
module.exports = {
defaultPath : function() {
var base = miscUtil.resolvePath('~/');
@ -15,6 +17,7 @@ module.exports = {
initFromFile : function(path, cb) {
var data = fs.readFileSync(path, 'utf8');
// :TODO: strip comments
this.config = JSON.parse(data);
},
@ -32,7 +35,11 @@ module.exports = {
usernameMax : 22,
passwordMin : 6,
requireActivation : true, // require SysOp activation?
defaultTheme : 'NU-MAYA',
},
defaults : {
theme : 'NU-MAYA',
passwordChar : '*',
},
paths : {

View File

@ -2,7 +2,8 @@
'use strict';
var ansi = require('./ansi_term.js');
var artwork = require('./art.js');
//var artwork = require('./art.js');
var theme = require('./theme.js');
var moduleUtil = require('./module_util.js');
var Log = require('./logger.js').log;
var Config = require('./config.js').config;
@ -74,7 +75,23 @@ function connectEntry(client) {
setTimeout(function onTimeout() {
term.write(ansi.clearScreen());
artwork.getArt('CONNECT', { random : true, readSauce : true }, function onArt(err, art) {
var dispOptions = {
name : 'CONNECT',
client : client,
};
// :TODO: if connect.js were a MenuModule, MCI/etc. would function here!
// ... could also avoid some of the boilerplate code
theme.displayThemeArt(dispOptions, function artDisplayed(err) {
var timeout = err ? 0 : 2000;
setTimeout(function timeout() {
client.gotoMenuModule( { name : Config.entryMod } );
}, timeout);
});
/*artwork.getArt('CONNECT', { random : true, readSauce : true }, function onArt(err, art) {
var timeout = 0;
if(!err) {
@ -88,6 +105,7 @@ function connectEntry(client) {
client.gotoMenuModule({ name : Config.entryMod } );
}, timeout);
});
*/
}, 500);
}

View File

@ -100,20 +100,9 @@ MCIViewFactory.prototype.createFromMCI = function(mci) {
setOption(1, 'textStyle');
if(options.textStyle === 'P') {
// Supply from theme, if available
// :TODO: Move this logic elsewhere
if(this.client.currentThemeInfo && this.client.currentThemeInfo.config) {
var themePwChar = this.client.currentThemeInfo.config.passwordChar;
if(_.isString(themePwChar)) {
options.textMaskChar = themePwChar.substr(0, 1);
} else if(_.isNumber(themePwChar)) {
options.textMaskChar = String.fromCharCode(themePwChar);
} else {
options.textMaskChar = '*';
}
} else {
options.textMaskChar = '*';
}
// Assign the proper password character / text mask
assert(_.isObject(this.client.currentThemeInfo));
options.textMaskChar = this.client.currentThemeInfo.getPasswordChar();
}
setFocusOption(0, 'focusTextStyle');

View File

@ -27,8 +27,14 @@ function MenuModule(options) {
self.beforeArt();
callback(null);
},
function displayArt(callback) {
theme.displayThemeArt(self.menuConfig.art, self.client, function onArt(err, mciMap) {
function displayArt(callback) {
var dispOptions = {
name : self.menuConfig.art,
font : self.menuConfig.font,
client : self.client,
};
theme.displayThemeArt(dispOptions, function onArt(err, mciMap) {
// :TODO: If the art simply is not found, or failed to load... we need to continue
if(err) {
console.log('TODO: log this error properly... maybe handle slightly diff.');

View File

@ -80,7 +80,7 @@ TextView.prototype.setFocus = function(focused) {
this.client.term.write(this.getANSIColor(this.getFocusColor()));
};
TextView.prototype.getViewData = function() {
TextView.prototype.getData = function() {
return this.text;
};

View File

@ -10,6 +10,7 @@ var fs = require('fs');
var paths = require('path');
var async = require('async');
var _ = require('lodash');
var assert = require('assert');
exports.getThemeInfo = getThemeInfo;
exports.getThemeArt = getThemeArt;
@ -27,6 +28,22 @@ function getThemeInfo(themeID, cb) {
try {
// :TODO: strip comments/etc. ala menu.json
var info = JSON.parse(data);
//
// Create some handy helpers
//
info.getPasswordChar = function() {
var pwChar = Config.defaults.passwordChar;
if(_.isObject(info.config)) {
if(_.isString(info.config.passwordChar)) {
pwChar = info.config.passwordChar.substr(0, 1);
} else if(_.isNumber(info.config.passwordChar)) {
pwChar = String.fromCharCode(info.config.passwordChar);
}
}
return pwChar;
};
cb(null, info);
} catch(e) {
cb(err);
@ -116,8 +133,12 @@ function getThemeArt(name, themeID, options, cb) {
});
}
function displayThemeArt(name, client, cb) {
getThemeArt(name, client.user.properties.art_theme_id, function onArt(err, artInfo) {
function displayThemeArt(options, cb) {
assert(_.isObject(options));
assert(_.isObject(options.client));
assert(_.isString(options.name));
getThemeArt(options.name, options.client.user.properties.art_theme_id, function onArt(err, artInfo) {
if(err) {
cb(err);
} else {
@ -128,7 +149,16 @@ function displayThemeArt(name, client, cb) {
}
}
art.display( { art : artInfo.data, client : client, iceColors : iceColors }, function onDisplayed(err, mci) {
var dispOptions = {
art : artInfo.data,
sauce : artInfo.sauce,
client : options.client,
iceColors : iceColors,
font : options.font,
};
art.display(dispOptions, function onDisplayed(err, mci) {
cb(err, mci, artInfo);
});
}

View File

@ -112,7 +112,7 @@ VerticalMenuView.prototype.onSpecialKeyPress = function(keyName) {
VerticalMenuView.super_.prototype.onSpecialKeyPress.call(this, keyName);
};
VerticalMenuView.prototype.getViewData = function() {
VerticalMenuView.prototype.getData = function() {
return this.focusedItemIndex;
};

View File

@ -187,5 +187,5 @@ View.prototype.onSpecialKeyPress = function(keyName) {
}
};
View.prototype.getViewData = function() {
View.prototype.getData = function() {
};

View File

@ -86,7 +86,7 @@ function ViewController(options) {
var viewData;
for(var id in self.views) {
try {
viewData = self.views[id].getViewData();
viewData = self.views[id].getData();
if(typeof viewData !== 'undefined') {
formData.value[id] = viewData;
}
@ -315,7 +315,7 @@ ViewController.prototype.loadFromMCIMapAndConfig = function(options, cb) {
//
if(_.isObject(formConfig.submit)) {
// :TODO: If this model is kept, formData does not need to include actual data, just form ID & submitID
// we can get the rest here via each view in form -> getViewData()
// we can get the rest here via each view in form -> getData()
self.on('submit', function onSubmit(formData) {
Log.debug( { formData : formData }, 'Submit form');
@ -419,7 +419,7 @@ ViewController.prototype.formatMCIString = function(format) {
return match;
}
return view.getViewData();
return view.getData();
});
};

View File

@ -56,7 +56,7 @@ function ApplyModule(menuConfig) {
affiliation : args.affils,
email_address : args.email,
web_address : args.web,
art_theme_id : Config.users.defaultTheme, // :TODO: allow '*' = random
art_theme_id : Config.defaults.theme, // :TODO: allow '*' = random
account_status : user.User.AccountStatus.inactive,
// :TODO: Other defaults

View File

@ -167,6 +167,7 @@
"newUserActive" : {
"art" : "NEWACT",
"options" : {
// :TODO: implement MCI codes for this
"clearScreen" : true
},
"form" : {