diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ca0a53..eb7203a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # `Change log` +--------------- +### 1.2.0 - 20.05.2023 +- JS files reworked and refactored +- Project Archived + ### 1.1.0 - 08.02.2023 - Restructure of the files (split into sever files/classes) and minor refactoring + minor text changes - Commit history reset + `README.MD` update diff --git a/README.md b/README.md index bfd64c2..1fea25c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## This tool is now `outdated` (only for game versions 1.39-1.42) and not supported, for new info how to create new custom fonts for ETS2/ATS visit [this forum thread](https://forum.scssoft.com/viewtopic.php?t=297332). +### This tool is `outdated` (only for game versions 1.39-1.42) and not supported. For new info on how to create new custom fonts for ETS2/ATS visit [this SCSsoft forum thread](https://forum.scssoft.com/viewtopic.php?t=297332). --- @@ -8,10 +8,10 @@ A simple converter of BMFont file `.fnt` to SCS' `.font` format for ETS2 and ATS Original idea by Etrusan. --- - ## How to use -1. Got to [releases section](https://github.com/Soundwave2142/MT-bmfont2scs/releases) of `MT-bmfont2scs` and download latest relase +1. Got to [releases section](https://github.com/Soundwave2142/MT-bmfont2scs/releases) of `MT-bmfont2scs` and download + the latest release 2. Extract contents of ZIP file anywhere and open `bmfont2scs.html` in your browser 3. Click "select file" button and select BMFont's `.fnt` file 4. The script will generate an archive with `.font` + `.mat` + `.tobj` files for you and start a download in browser @@ -19,9 +19,12 @@ Original idea by Etrusan. It is recommended to set up the right path in your .fnt file `file=` before converting. That path will be used in all generated files. +--- ## Support the author [](https://www.paypal.com/donate?hosted_button_id=FUN3Z5RJCQEWL) +--- ## Other tools for ETS2/ATS + ### [Trucking Tool Exploration Merger](https://github.com/Soundwave2142/TT-exploration-merger) diff --git a/bmfont2scs.html b/bmfont2scs.html index a4db22b..a483730 100644 --- a/bmfont2scs.html +++ b/bmfont2scs.html @@ -8,7 +8,7 @@ - + @@ -32,6 +32,11 @@

Github link

+

+ It is recommended to set up the right path in your .fnt file 'file=>path<' before converting. That path will be used + in all generated files. +

+
Select bmfont file (.fnt) : diff --git a/resources/js/bmfont2scs-file-creator.js b/resources/js/bmfont2scs-file-creator.js new file mode 100644 index 0000000..1842ad3 --- /dev/null +++ b/resources/js/bmfont2scs-file-creator.js @@ -0,0 +1,158 @@ +/** + * @author Soundwave2142 + * + * https://github.com/Soundwave2142 + * https://sw-projects.net/ + * + * Original idea by Etrusan + * + * 02.28.2021 + */ + +class BMFont2SCSFileCreator { + + /** + * @type {JSZip} + */ + zip = new JSZip(); + + /** + * @type {BMFont2SCSHelper} + */ + helper = new BMFont2SCSHelper(); + + /** + * @type {{kerningKeepIfLimit: boolean, generalInfo: [], kerningLimit: number}} + */ + config = { + kerningLimit: 1535, + kerningKeepIfLimit: true, + generalInfo: [] + } + + /** + * @param config + */ + constructor(config) { + this.config = $.extend(this.config, config); + } + + /** + * @param fileName {string} + * @param chars {array} + * @param kernings {array} + * @param contents {string} + * + * @returns {BMFont2SCSFileCreator} + */ + createFontFile(fileName, chars, kernings, contents = '') { + const self = this; + + // add general info + this.config.generalInfo.forEach(function (line) { + contents += line + '\n'; + }) + + // add header and insert coordinates for chars + contents += '\n' + '#NUM, P_x, P_y, W, H, L, T, A, I # character / glyph name\n\n'; + chars.forEach(function (setOfCoordinates) { + setOfCoordinates.forEach(function (coordinate, key) { + const spacing = self.calculateSpacingForCoordinate(coordinate, key) + const delimiter = setOfCoordinates.length > (key + 1) ? ',' : ''; + + contents += spacing + coordinate + delimiter; + }); + + contents += " # '" + self.helper.getUnicodeFromHex(setOfCoordinates[0]) + "'\n"; + }); + + const BreakException = {}; + + // add header and insert coordinates for kerning + if (kernings.length < this.config.kerningLimit || this.config.kerningKeepIfLimit) { + contents += '\n# kerning...\n\n'; + + try { + kernings.forEach(function (coordinates, key) { + if (key >= self.config.kerningLimit) { + throw BreakException // throw a break, if kerning reached its limit + } + + contents += 'kern: ' + coordinates[0] + ', ' + coordinates[1] + ', ' + coordinates[2] + ' # \'' + + self.helper.getUnicodeFromHex(coordinates[0]) + '\' -> \'' + + self.helper.getUnicodeFromHex(coordinates[1]) + '\'\n'; + }); + } catch (e) { + if (e !== BreakException) throw e; + } + } + + this.zip.file(fileName + '.font', contents); + + return this; + } + + /** + * @param coordinate {string} + * @param coordinatePosition {int} + * @param defaultSpacing {string} + * + * @returns {string} + */ + calculateSpacingForCoordinate(coordinate, coordinatePosition, defaultSpacing = '') { + // have bigger spacing after first (hex) value, rest are smaller (4) + const maxSpacing = coordinatePosition === 1 ? 7 : 4; + + for (let i = 0; i < (maxSpacing - coordinate.length); i++) { + defaultSpacing += ' '; + } + + return defaultSpacing; + } + + /** + * @param fileName {string} + * @param contents {string} + * + * @returns {BMFont2SCSFileCreator} + */ + createMatFile(fileName, contents = '') { + contents = 'material : "ui.white_font" {\n' + + ' texture : "' + fileName + '.tobj"\n' + + ' texture_name : "texture"\n' + + '}\n'; + + this.zip.file(fileName + '.mat', contents); + + return this; + } + + /** + * @param fileName {string} + * @param contents {string} + * + * @returns {BMFont2SCSFileCreator} + */ + createTobjFile(fileName, contents = '') { + contents = 'map 2d ' + fileName + '.tga\n' + + 'addr\n' + + ' clamp_to_edge\n' + + ' clamp_to_edge\n' + + 'color_space linear\n' + + 'nomips\n' + + 'nocompress\n'; + + this.zip.file(fileName + '.tobj', contents); + + return this; + } + + /** + * @param name + */ + saveZip(name) { + this.zip.generateAsync({type: 'blob'}).then(function (blob) { + saveAs(blob, name + '.zip'); + }); + } +} diff --git a/resources/js/bmfont2scs-helper.js b/resources/js/bmfont2scs-helper.js index 7b27f26..58b7abd 100644 --- a/resources/js/bmfont2scs-helper.js +++ b/resources/js/bmfont2scs-helper.js @@ -7,7 +7,6 @@ * Original idea by Etrusan * * 02.28.2021 - * */ class BMFont2SCSHelper { @@ -18,7 +17,7 @@ class BMFont2SCSHelper { * * @returns {number} */ - sortFunction(a, b) { + sortFunction = function (a, b) { if (a[0] === b[0]) { return 0; } else { @@ -41,10 +40,10 @@ class BMFont2SCSHelper { * @returns {string} */ convertDecToHex(val) { - var element = parseInt(val, 10).toString(16); - var prefix = 'x'; + const element = parseInt(val, 10).toString(16); + let prefix = 'x'; - for (var i = element.length; i < 4; i++) { + for (let i = element.length; i < 4; i++) { prefix += '0'; } diff --git a/resources/js/bmfont2scs-ziper.js b/resources/js/bmfont2scs-ziper.js deleted file mode 100644 index e3529dd..0000000 --- a/resources/js/bmfont2scs-ziper.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @author Soundwave2142 - * - * https://github.com/Soundwave2142 - * https://sw-projects.net/ - * - * Original idea by Etrusan - * - * 02.28.2021 - * - */ - -class BMFont2SCSZipper { - - /** - * @type {JSZip} - */ - zip = new JSZip(); - - /** - * @param name - * @param format - * @param contents - * - * @returns {BMFont2SCSZipper} - */ - createFile(name, format, contents) { - this.zip.file(name + '.' + format, contents); - - return this; - } - - /** - * @param name - */ - saveFile(name) { - this.zip.generateAsync({type: 'blob'}).then(function (blob) { - saveAs(blob, name + '.zip'); - }); - } -} diff --git a/resources/js/bmfont2scs.js b/resources/js/bmfont2scs.js index 0af32a4..378d134 100644 --- a/resources/js/bmfont2scs.js +++ b/resources/js/bmfont2scs.js @@ -7,22 +7,16 @@ * Original idea by Etrusan * * 02.28.2021 - * - * TODO: this is a mess, refactor - * */ - /* set up converter events when page is done loading */ $(document).ready(function () { - const converter = new BMFont2SCS(); - $("#file-input").on('change', function () { $("#convert-btn").show(); }); $('#convert-btn').on('click', function () { - converter.convert(); + (new BMFont2SCS()).convert(); }); }); @@ -30,143 +24,135 @@ $(document).ready(function () { * Convertor class */ class BMFont2SCS { + /** - * @type {{kernelLimit: number}} + * @type {BMFont2SCSHelper} */ - config = { - kernelLimit: 1535 - } + helper = new BMFont2SCSHelper(); /** - * @type {FileReader} + * @type {string} */ - fr = new FileReader(); + fileContents = ''; /** - * @type {BMFont2SCSHelper} + * @type {[]} */ - helper = new BMFont2SCSHelper(); + chars = []; + + /** + * @type {[]} + */ + kernings = []; + /** + * Assign on load event then load the file + */ convert() { - const that = this; + const self = this; + const fileReader = new FileReader(); + + fileReader.onload = function () { + // load file reader result into file, then handle each line separately. + self.fileContents = fileReader.result; + self.fileContents.split(/\r?\n/).forEach(function (line) { + self.handleLine(line) + }); - this.fr.onload = function () { - that.onFileLoad(that); + self.outputData(); } - this.fr.readAsText($("#file-input").prop('files')[0]); + fileReader.readAsText($("#file-input").prop('files')[0]); } - onFileLoad = function (that) { - let generalInfo = { - vert_span: that.helper.find(that.fr.result, 'lineHeight='), - line_spacing: 0, - width: that.helper.find(that.fr.result, 'scaleW='), - height: that.helper.find(that.fr.result, 'scaleH='), - filename: that.helper.find(that.fr.result, 'file="').split('.')[0] - }; - - let kerning = [], coordinates = []; - - that.fr.result.split(/\r?\n/).forEach(function (line) { - let result; - let isKerningLine = line.startsWith("kerning first="); - - if (line.startsWith("char id=") || isKerningLine) { - result = that.handleData(line, isKerningLine); - if (result) { - coordinates.push(result); - } - } - }); + /** + * @param line + */ + handleLine(line) { + let isCharLine = line.startsWith("char id="); + let isKerningLine = line.startsWith("kerning first="); + if (!isCharLine && !isKerningLine) { + return; + } - kerning.sort(that.sortFunction); - that.outputData(generalInfo, coordinates, kerning); + this.handleLineData(line, isCharLine, isKerningLine); } /** - * @param item - * @param kerning - * @returns {*} + * @param line {string} + * @param isCharLine {boolean} + * @param isKerningLine {boolean} + * + * @returns {boolean|*} */ - handleData(item, kerning = false) { - var arrayCoords = []; - - item.replace(/[^0-9 \-+]+/g, "").split(" ").forEach(function (coords) { - if (coords !== '') { - arrayCoords.push(coords); - } - }); - - if (arrayCoords.length > 0) { - arrayCoords[0] = this.helper.convertDecToHex(arrayCoords[0]); - - if (!kerning) { - arrayCoords.pop(); - } else { - arrayCoords[1] = this.helper.convertDecToHex(arrayCoords[1]); - } - - return arrayCoords; + handleLineData(line, isCharLine, isKerningLine) { + // remove every non number symbol, split the line into chars to filter out empty chars + // the change type of coordinate to int for further processing + const coordinates = line.replace(/[^0-9 \-+]+/g, "").split(" ") + .filter(number => number.trim().length !== 0); // .map(coordinate => parseInt(coordinate)); <-- currently int not needed. + + if (coordinates.length <= 0) { + return; } - return false; + if (isCharLine) { + this.handleCharCoordinates(coordinates); + } else if (isKerningLine) { + this.handleKerningCoordinates(coordinates); + } } /** - * @param info - * @param coords - * @param kerning + * @param coordinates */ - outputData(info, coords, kerning) { - let that = this; - var result = 'vert_span:' + info.vert_span + '\nline_spacing:' + info.line_spacing + '\n\nimage:' + info.filename + '.mat, ' + info.width + ', ' + info.height + '\n\n'; - - result += '#NUM, P_x, P_y, W, H, L, T, A, I # character / glyph name\n\n'; - coords.forEach(function (setOfCoords) { - - setOfCoords.forEach(function (coord, key) { - var itemLength = coord.length; - var max = key > 2 ? 3 : 5; - - coord += setOfCoords.length > (key + 1) ? ',' : ''; - for (var i = 0; i < (max - itemLength); i++) { - coord = ' ' + coord; - } - - result += coord; - }); + handleCharCoordinates(coordinates) { + coordinates[0] = this.helper.convertDecToHex(coordinates[0]); // convert first to hex, which is id + coordinates.pop(); // and pop last, which is "chnl" and not needed - result += ' # \'' + that.helper.getUnicodeFromHex(setOfCoords[0]) + '\'\n'; - }); + this.chars.push(coordinates); + } - if (kerning.length < this.config.kerningLimit || document.querySelector('input[name="kerningOption"]:checked').value === 'keep') { - result += '\n# kerning...\n\n'; + /** + * @param coordinates + */ + handleKerningCoordinates(coordinates) { + // both first and second element are ids and needed to be in hex format + coordinates[0] = this.helper.convertDecToHex(coordinates[0]); + coordinates[1] = this.helper.convertDecToHex(coordinates[1]); - var BreakException = {}; + this.kernings.push(coordinates); + } - try { - kerning.forEach(function (setOfKerns, key) { - if (key >= that.config.kerningLimit) { - throw BreakException - } - result += 'kern: ' + setOfKerns[0] + ', ' + setOfKerns[1] + ', ' + setOfKerns[2] + ' # \'' - + that.helper.getUnicodeFromHex(setOfKerns[0]) + '\' -> \'' - + that.helper.getUnicodeFromHex(setOfKerns[1]) + '\'\n'; - }); - } catch (e) { - if (e !== BreakException) throw e; - } - } + /** + * pack processed lines into files and pack files into zip + */ + outputData() { + this.kernings.sort(this.helper.sortFunction); + + const fileName = this.findInFile('file="').split('.')[0] + const fileConfig = { + kerningKeepIfLimit: $('input[name="kerningOption"]:checked').val() === 'keep', + generalInfo: [ + 'vert_span:' + this.findInFile('lineHeight='), + 'line_spacing:' + '0', + 'image:' + fileName + '.mat, ' + this.findInFile('scaleW=') + ', ' + this.findInFile('scaleH=') + ] + }; - let mat = 'material : "ui.white_font" {\n texture : "' + info.filename + '.tobj"\n texture_name : "texture"\n}\n'; - let tobj = 'map 2d ' + info.filename + '.tga\naddr\n clamp_to_edge\n clamp_to_edge\ncolor_space linear\nnomips\nnocompress\n'; + (new BMFont2SCSFileCreator(fileConfig)) // create three needed files and pack them to zip + .createFontFile(fileName, this.chars, this.kernings) + .createMatFile(fileName) + .createTobjFile(fileName) + .saveZip('bmfont2scs_' + fileName); + } - this.zip = new BMFont2SCSZipper(); - this.zip.createFile(info.filename, 'font', result) - .createFile(info.filename, 'mat', mat) - .createFile(info.filename, 'tobj', tobj) - .saveFile('bmfont2scs_' + info.filename) + /** + * @param key + * + * @returns {*|string} + */ + findInFile(key) { + return this.helper.find(this.fileContents, key); } }