From c71d4b4d58fa4208c0ab8068f646ae9f0871e998 Mon Sep 17 00:00:00 2001 From: JinhyeokLee Date: Tue, 28 Jun 2022 02:55:27 +0900 Subject: [PATCH 1/4] Add GFM alignment, Measure character width based on browser renderer This sacrifices performance to better support unicode characters. --- assets/js/script.js | 50 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/assets/js/script.js b/assets/js/script.js index 28b289f..88203c6 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -13,12 +13,38 @@ $(function() { this.selectionStart = startPos + myValue.length; this.selectionEnd = startPos + myValue.length; this.scrollTop = scrollTop; - + e.preventDefault(); } }); + + elMeasure = document.createElement('div'); + $(elMeasure).css({ + position: 'absolute', + top: '-200px', + width: 'auto', + height: 'auto', + display: 'inline-block', + font: $('#input').css('font'), + }); + + document.body.appendChild(elMeasure); + elMeasure.innerText = "."; + wDot = elMeasure.clientWidth; }); + +var elMeasure, wDot; +function getUnicodeAwareLength(str) { + elMeasure.innerText = str; + return Math.round(elMeasure.clientWidth / wDot); +} + +// var regMultibyte = /[\uD800-\uDBFF][\uD800-\uDFFF]{,13}|[ㄱ-ㅎㅏ-ㅣ가-힣]|(?:[\x23-\x39\uFE0F\u20E3]{3})/g; +// function getUnicodeAwareLength(str) { +// return str.replace(regMultibyte, '..').length; +// } + function createTable() { // set up the style var cTL, cTM, cTR; @@ -79,12 +105,12 @@ function createTable() { if (autoFormat) { if (hasHeaders && i == 0 && !spreadSheetStyle) { ; // a header is allowed to not be a number (exclude spreadsheet because the header hasn't been added yet - } else if (isNumberCol[j] && !data.match(/^(\s*-?(\d|,| |[.])*\s*)$/)) { //number can be negative, comma/period-separated, or decimal + } else if (isNumberCol[j] && !data || !data.match(/^(\s*-?(\d|,| |[.])*\s*)$/)) { //number can be negative, comma/period-separated, or decimal, allow empty cell isNumberCol[j] = false; } } - if (isNewCol || colLengths[j] < data.length) { - colLengths[j] = data.length; + if (isNewCol || colLengths[j] < data.length) { + colLengths[j] = getUnicodeAwareLength(data); } } } @@ -372,7 +398,7 @@ function createTable() { // output the top most row // Ex: +---+---+ - if (hasTopLine ) { + if (hasTopLine) { if (topLineUsesBodySeparators || !hasHeaders) { topLineHorizontal = spH; } else { @@ -381,11 +407,12 @@ function createTable() { output += getSeparatorRow(colLengths, cTL, cTM, cTR, topLineHorizontal, prefix, suffix) } + var separatorAlign = (style === "gfm" && autoFormat) ? isNumberCol : [] for (var i = 0; i < rows.length; i++) { // Separator Rows if (hasHeaders && hasHeaderSeparators && i == 1 ) { // output the header separator row - output += getSeparatorRow(colLengths, cML, cMM, cMR, hdH, prefix, suffix) + output += getSeparatorRow(colLengths, cML, cMM, cMR, hdH, prefix, suffix, separatorAlign) } else if ( hasLineSeparators && i < rows.length ) { // output line separators if( ( !hasHeaders && i >= 1 ) || ( hasHeaders && i > 1 ) ) { @@ -444,13 +471,14 @@ function createTable() { $('#outputTbl').hide(); } -function getSeparatorRow(lengths, left, middle, right, horizontal, prefix, suffix) { +function getSeparatorRow(lengths, left, middle, right, horizontal, prefix, suffix, isNumberCol) { rowOutput = prefix; for (var j = 0; j <= lengths.length; j++) { + var alignSuffix = isNumberCol && isNumberCol.length > j && isNumberCol[j] ? ":" : ""; if ( j == 0 ) { - rowOutput += left + _repeat(horizontal, lengths[j] + 2); + rowOutput += left + _repeat(horizontal, lengths[j] + 2 - alignSuffix.length) + alignSuffix; } else if ( j < lengths.length ) { - rowOutput += middle + _repeat(horizontal, lengths[j] + 2); + rowOutput += middle + _repeat(horizontal, lengths[j] + 2 - alignSuffix.length) + alignSuffix; } else { rowOutput += right + suffix + "\n"; } @@ -609,8 +637,8 @@ function defValue(value, defaultValue) { function _pad(text, length, char, align) { // align: r l or c char = defValue(char, " "); - align = defValue(align, "l"); - var additionalChars = length - text.length; + align = defValue(align, "l"); + var additionalChars = length - getUnicodeAwareLength(text); var result = ""; switch (align) { case "r": From dae7a1cce3519481f1c7ca77c09b5b6508ebc281 Mon Sep 17 00:00:00 2001 From: JinhyeokLee Date: Tue, 28 Jun 2022 03:45:47 +0900 Subject: [PATCH 2/4] Add width cache, add more trim() checks --- assets/js/script.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/assets/js/script.js b/assets/js/script.js index 88203c6..382c501 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -18,7 +18,8 @@ $(function() { } }); - elMeasure = document.createElement('div'); + elMeasure = document.createElement('pre'); + document.body.appendChild(elMeasure); $(elMeasure).css({ position: 'absolute', top: '-200px', @@ -26,18 +27,19 @@ $(function() { height: 'auto', display: 'inline-block', font: $('#input').css('font'), + padding: 0 }); - document.body.appendChild(elMeasure); - elMeasure.innerText = "."; - wDot = elMeasure.clientWidth; + elMeasure.innerText = "x"; + wSingle = elMeasure.clientWidth; }); -var elMeasure, wDot; +var elMeasure, wSingle, _cache = {}; function getUnicodeAwareLength(str) { - elMeasure.innerText = str; - return Math.round(elMeasure.clientWidth / wDot); + if (_cache[str] )return _cache[str] + elMeasure.innerText = str; + return _cache[str] = Math.round(elMeasure.clientWidth / wSingle); } // var regMultibyte = /[\uD800-\uDBFF][\uD800-\uDFFF]{,13}|[ㄱ-ㅎㅏ-ㅣ가-힣]|(?:[\x23-\x39\uFE0F\u20E3]{3})/g; @@ -84,6 +86,7 @@ function createTable() { // calculate the max size of each column var colLengths = []; var isNumberCol = []; + _cache = {}; for (var i = 0; i < rows.length; i++) { if (trimInput) { rows[i] = rows[i].trim(); @@ -96,7 +99,7 @@ function createTable() { } var cols = rows[i].split(separator); for (var j = 0; j < cols.length; j++) { - var data = cols[j]; + var data = cols[j].trim(); var isNewCol = colLengths[j] == undefined; if (isNewCol) { isNumberCol[j] = true; @@ -109,7 +112,7 @@ function createTable() { isNumberCol[j] = false; } } - if (isNewCol || colLengths[j] < data.length) { + if (isNewCol || colLengths[j] < getUnicodeAwareLength(data)) { colLengths[j] = getUnicodeAwareLength(data); } } @@ -426,7 +429,7 @@ function createTable() { output += prefix; } var cols = rows[i].split(separator); - var data = cols[j] || ""; + var data = (cols[j] || "").trim(); if (autoFormat) { if (hasHeaders && i == 0) { align = "c"; From ebb45852c0be3ea2197dfb8f6c82cebb9512e6dd Mon Sep 17 00:00:00 2001 From: JinhyeokLee Date: Tue, 28 Jun 2022 04:07:15 +0900 Subject: [PATCH 3/4] Improve width calculation accuracy --- assets/css/style.css | 2 +- assets/js/script.js | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/assets/css/style.css b/assets/css/style.css index f58220b..7ad5965 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -55,7 +55,7 @@ html, body { } .fixed-width { - font-family: courier new, monospace; + font-family: monospace; } #main { diff --git a/assets/js/script.js b/assets/js/script.js index 382c501..2d61dc1 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -27,11 +27,14 @@ $(function() { height: 'auto', display: 'inline-block', font: $('#input').css('font'), - padding: 0 + letterSpacing: 0, + padding: 0, + border: 0, + fontVariantLigatures: 'none' }); - elMeasure.innerText = "x"; - wSingle = elMeasure.clientWidth; + elMeasure.innerText = Array(11).join('x'); + wSingle = elMeasure.clientWidth / 10; }); From 7201a53c54576a40bfa72d32d2e7a120d37d9c7f Mon Sep 17 00:00:00 2001 From: JinhyeokLee Date: Wed, 29 Jun 2022 02:37:34 +0900 Subject: [PATCH 4/4] Change to Canvas based measurement, refactor a bit --- assets/js/script.js | 72 ++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/assets/js/script.js b/assets/js/script.js index 2d61dc1..18f8b83 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -13,42 +13,27 @@ $(function() { this.selectionStart = startPos + myValue.length; this.selectionEnd = startPos + myValue.length; this.scrollTop = scrollTop; - + e.preventDefault(); } }); - - elMeasure = document.createElement('pre'); - document.body.appendChild(elMeasure); - $(elMeasure).css({ - position: 'absolute', - top: '-200px', - width: 'auto', - height: 'auto', - display: 'inline-block', - font: $('#input').css('font'), - letterSpacing: 0, - padding: 0, - border: 0, - fontVariantLigatures: 'none' - }); - - elMeasure.innerText = Array(11).join('x'); - wSingle = elMeasure.clientWidth / 10; -}); + _canvas = document.createElement('canvas'); +}); -var elMeasure, wSingle, _cache = {}; -function getUnicodeAwareLength(str) { - if (_cache[str] )return _cache[str] - elMeasure.innerText = str; - return _cache[str] = Math.round(elMeasure.clientWidth / wSingle); +var _canvas, _context, _cache = {}; // measuring cache +function measureWidth(str) { + if (typeof _cache[str] === 'number') return _cache[str]; + _context = _canvas.getContext('2d'); + _context.font = $('#input').css('font'); + return _cache[str] = _context.measureText(str).width; +} +function getUnicodeAwareLength(text) { + var w = measureWidth(text); + var wSingle = measureWidth("-"); + return w / wSingle; } -// var regMultibyte = /[\uD800-\uDBFF][\uD800-\uDFFF]{,13}|[ㄱ-ㅎㅏ-ㅣ가-힣]|(?:[\x23-\x39\uFE0F\u20E3]{3})/g; -// function getUnicodeAwareLength(str) { -// return str.replace(regMultibyte, '..').length; -// } function createTable() { // set up the style @@ -115,8 +100,9 @@ function createTable() { isNumberCol[j] = false; } } - if (isNewCol || colLengths[j] < getUnicodeAwareLength(data)) { - colLengths[j] = getUnicodeAwareLength(data); + var colLength = Math.round(getUnicodeAwareLength(data)); + if (isNewCol || colLengths[j] < colLength) { + colLengths[j] = colLength; } } } @@ -425,7 +411,7 @@ function createTable() { output += getSeparatorRow(colLengths, cML, cMM, cMR, spH, prefix, suffix) } } - + var carryOver = 0; // start a new line for (var j = 0; j <= colLengths.length; j++) { // output the data if (j == 0) { @@ -447,8 +433,21 @@ function createTable() { } else { verticalBar = spV; } + // compensate fraction width if ( j < colLengths.length ) { - data = _pad(data, colLengths[j], " ", align); + var colLength = colLengths[j]; + // calculate fractional error + var txtLength = getUnicodeAwareLength(data); + var txtError = colLength > txtLength ? Math.ceil(txtLength) - txtLength : 0; + carryOver += txtError; + + data = _pad(data, colLength, " ", align); + + // add a space to compensate for fraction width + if (carryOver >= 0.75) { + data += ' ' + carryOver -= 1; + } if (j == 0 && !hasLeftSide) { output += " " + data + " "; } else { @@ -481,10 +480,11 @@ function getSeparatorRow(lengths, left, middle, right, horizontal, prefix, suffi rowOutput = prefix; for (var j = 0; j <= lengths.length; j++) { var alignSuffix = isNumberCol && isNumberCol.length > j && isNumberCol[j] ? ":" : ""; + var colLength = lengths[j] + 2 - alignSuffix.length; if ( j == 0 ) { - rowOutput += left + _repeat(horizontal, lengths[j] + 2 - alignSuffix.length) + alignSuffix; + rowOutput += left + _repeat(horizontal, colLength) + alignSuffix; } else if ( j < lengths.length ) { - rowOutput += middle + _repeat(horizontal, lengths[j] + 2 - alignSuffix.length) + alignSuffix; + rowOutput += middle + _repeat(horizontal, colLength) + alignSuffix; } else { rowOutput += right + suffix + "\n"; } @@ -644,7 +644,7 @@ function _pad(text, length, char, align) { // align: r l or c char = defValue(char, " "); align = defValue(align, "l"); - var additionalChars = length - getUnicodeAwareLength(text); + var additionalChars = Math.floor(length - (getUnicodeAwareLength(text))); var result = ""; switch (align) { case "r":