Skip to content

Commit 9d5fcbc

Browse files
committed
refactor: reset modifiers on newline
fix: multiple newlines not working build: bump to version 1.0.2
1 parent 33af223 commit 9d5fcbc

File tree

4 files changed

+68
-68
lines changed

4 files changed

+68
-68
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "minecraft-text-canvas",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "Generate images of Minecraft text using markup.",
55
"license": "MIT",
66
"main": "dist/index.js",

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const FONT_SIZE = 8;
22
export const FONT_OFFSET = 1;
33
export const SUPPORTED_MODIFIERS_GLOBAL = /&(?:0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|l|r)/g;
4+
export const NEWLINE_REGEX = /\r?\n/;
45
export const TEXT_COLORS = {
56
'0': 0x000,
67
'1': 0x00a,

src/lib/getFormattedWidth.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,21 @@
11
import { CanvasRenderingContext2D } from 'canvas';
2-
import { FONT_OFFSET, SUPPORTED_MODIFIERS_GLOBAL } from '../constants';
2+
import { FONT_OFFSET, NEWLINE_REGEX, SUPPORTED_MODIFIERS_GLOBAL } from '../constants';
33

44
const BOLD_REGEX = /&l(.*?)(?:&r|$)/g;
55

66
export default function getFormattedWidth(unformattedText: string, ctx: CanvasRenderingContext2D) {
77
let greatestFormattedWidth = 0;
8-
let isBold = false;
98

109
// Remove all modifiers and split by line break to calculate text width
11-
const wrappedText = unformattedText.split(/\r\n|\r|\n/g);
12-
wrappedText.forEach((line) => {
10+
unformattedText.split(NEWLINE_REGEX).forEach((line) => {
1311
let lineWidth = ctx.measureText(line.replaceAll(SUPPORTED_MODIFIERS_GLOBAL, '')).width;
1412

15-
// If bold carries over from previous line, prefix the line with '&l' so it fits the regex
16-
const boldSubstrings = isBold ? `&l${line}`.match(BOLD_REGEX) : line.match(BOLD_REGEX);
17-
if (boldSubstrings) {
18-
// Check if the bold modifier extends to the next line
19-
isBold = !boldSubstrings[-1]?.endsWith('&r');
20-
21-
boldSubstrings.forEach((subStr) => {
22-
// Remove all modifiers in the text
23-
const subStrRemovedModifiers = subStr.replaceAll(SUPPORTED_MODIFIERS_GLOBAL, '');
24-
lineWidth += subStrRemovedModifiers.length * FONT_OFFSET;
25-
});
26-
}
13+
const boldSubstrings = line.match(BOLD_REGEX);
14+
boldSubstrings?.forEach((subStr) => {
15+
// Remove all modifiers in the text
16+
const subStrRemovedModifiers = subStr.replaceAll(SUPPORTED_MODIFIERS_GLOBAL, '');
17+
lineWidth += subStrRemovedModifiers.length * FONT_OFFSET;
18+
});
2719

2820
// If this is the widest line, set the overall width accordingly
2921
if (greatestFormattedWidth < lineWidth) {

src/lib/renderText.ts

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,68 +5,75 @@ import {
55
SUPPORTED_MODIFIERS_GLOBAL,
66
TEXT_COLORS,
77
TEXT_SHADOW_COLORS,
8+
NEWLINE_REGEX,
89
} from '../constants';
910

1011
export default function renderText(text: string, ctx: CanvasRenderingContext2D) {
11-
let cursorX = 0;
1212
let cursorY = FONT_SIZE - FONT_OFFSET;
13-
let currentColor: number = TEXT_COLORS.f;
14-
let currentShadowColor: number = TEXT_SHADOW_COLORS.f;
15-
let isBold = false;
1613

17-
for (let i = 0; i < text.length; i++) {
18-
const char = text[i] as string;
19-
const nextChar = text[i + 1];
14+
text.split(NEWLINE_REGEX).forEach((line) => {
15+
let currentColor: number = TEXT_COLORS.f;
16+
let currentShadowColor: number = TEXT_SHADOW_COLORS.f;
17+
let isBold = false;
18+
let cursorX = 0;
2019

21-
// Check if this character and the next one are a supported modifier
22-
// WARNING: JavaScript has inconsistent RegEx test behaviour with the global flag enabled -
23-
// On subsequent test() calls, it begins searching from the index of the previous match.
24-
// For this reason, we have to create a new, non-global RegEx to test our expression.
25-
// I hate JS.
26-
if (char === '&' && new RegExp(SUPPORTED_MODIFIERS_GLOBAL.source).test(char + nextChar)) {
27-
if (nextChar && nextChar in TEXT_COLORS) {
28-
currentColor = TEXT_COLORS[nextChar as HexDigit];
29-
currentShadowColor = TEXT_SHADOW_COLORS[nextChar as HexDigit];
30-
} else if (nextChar === 'r') {
31-
currentColor = TEXT_COLORS.f;
32-
currentShadowColor = TEXT_SHADOW_COLORS.f;
33-
isBold = false;
34-
} else if (nextChar === 'l') {
35-
isBold = true;
20+
for (let i = 0; i < line.length; i++) {
21+
const char = line[i] as string;
22+
const nextChar = line[i + 1];
23+
24+
// Check if this character and the next one are a supported modifier
25+
// WARNING: JavaScript has inconsistent RegEx test behaviour with the global flag enabled -
26+
// On consecutive test() calls, it begins searching from the index of the previous match.
27+
// For this reason, we have to create a new, non-global RegEx to test our expression. I hate JS.
28+
if (
29+
nextChar &&
30+
char === '&' &&
31+
new RegExp(SUPPORTED_MODIFIERS_GLOBAL.source).test(char + nextChar)
32+
) {
33+
if (nextChar in TEXT_COLORS) {
34+
currentColor = TEXT_COLORS[nextChar as HexDigit];
35+
currentShadowColor = TEXT_SHADOW_COLORS[nextChar as HexDigit];
36+
} else if (nextChar === 'r') {
37+
currentColor = TEXT_COLORS.f;
38+
currentShadowColor = TEXT_SHADOW_COLORS.f;
39+
isBold = false;
40+
} else if (nextChar === 'l') {
41+
isBold = true;
42+
} else {
43+
continue;
44+
}
45+
46+
// Skip the next character as it is part of a modifying sequence
47+
i += 1;
3648
} else {
37-
continue;
38-
}
49+
const shadowX = cursorX + FONT_OFFSET;
50+
const shadowY = cursorY + FONT_OFFSET;
3951

40-
// Skip the next character as it is part of a modifying sequence
41-
i += 1;
42-
} else if (char === '\n') {
43-
cursorX = 0;
44-
cursorY = FONT_SIZE * 2 + FONT_OFFSET;
45-
} else {
46-
const shadowX = cursorX + FONT_OFFSET;
47-
const shadowY = cursorY + FONT_OFFSET;
52+
// Draw shadow
53+
ctx.fillStyle = `#${currentShadowColor.toString(16)}`;
54+
ctx.fillText(char, shadowX, shadowY);
55+
if (isBold) {
56+
ctx.fillText(char, shadowX + FONT_OFFSET, shadowY);
57+
}
4858

49-
// Draw shadow
50-
ctx.fillStyle = `#${currentShadowColor.toString(16)}`;
51-
ctx.fillText(char, shadowX, shadowY);
52-
if (isBold) {
53-
ctx.fillText(char, shadowX + FONT_OFFSET, shadowY);
54-
}
59+
// Draw text
60+
ctx.fillStyle = `#${currentColor.toString(16)}`;
61+
ctx.fillText(char, cursorX, cursorY);
62+
if (isBold) {
63+
ctx.fillText(char, cursorX + FONT_OFFSET, cursorY);
64+
}
5565

56-
// Draw text
57-
ctx.fillStyle = `#${currentColor.toString(16)}`;
58-
ctx.fillText(char, cursorX, cursorY);
59-
if (isBold) {
60-
ctx.fillText(char, cursorX + FONT_OFFSET, cursorY);
61-
}
66+
// Add extra space in between letters if character is bold
67+
let { width } = ctx.measureText(char);
68+
if (isBold) {
69+
width += FONT_OFFSET;
70+
}
6271

63-
// Add extra space in between letters if character is bold
64-
let { width } = ctx.measureText(char);
65-
if (isBold) {
66-
width += FONT_OFFSET;
72+
cursorX += width;
6773
}
68-
69-
cursorX += width;
7074
}
71-
}
75+
76+
// Move the cursor down to the next line. Add double-spacing to account for shadow
77+
cursorY += FONT_SIZE + (FONT_OFFSET * 2);
78+
});
7279
}

0 commit comments

Comments
 (0)