Skip to content

Commit dd6ef57

Browse files
Add badge image preview
1 parent b9ee4f1 commit dd6ef57

File tree

6 files changed

+221
-5
lines changed

6 files changed

+221
-5
lines changed

public/Mona-Sans.woff2

134 KB
Binary file not shown.

public/back.png

3.12 KB
Loading

public/index.html

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<title>QR Code Generator</title>
6+
<title>GitHub Badger Generator</title>
77
<link rel="stylesheet" href="https://unpkg.com/@primer/css@^20.2.4/dist/primer.css"/>
88
<link rel="stylesheet" href="./styles.css">
99
<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
10+
<link rel="preload" href="./Mona-Sans.woff2" as="font" type="font/woff2" crossorigin>
1011
</head>
1112
<body>
1213
<div class="container">
1314
<div class="input-section">
14-
<h1>QR Code Generator</h1>
15+
<h1>Badger Press</h1>
1516
<label for="githubhandle">GitHub Handle:</label>
1617
<input type="text" id="githubhandle" name="githubhandle">
1718
<label for="firstname">First Name:</label>
@@ -26,6 +27,23 @@ <h1>QR Code Generator</h1>
2627
<input type="text" id="pronouns" name="pronouns">
2728
</div>
2829
<div class="qr-section">
30+
<div class="badge-preview">
31+
<canvas id="badgeCanvas" width="296" height="128"></canvas>
32+
<div class="badge-preview-buttons">
33+
<button class="btn btn-primary mt-2" onclick="downloadBadge()">
34+
<svg class="octicon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
35+
<path d="M7.47 10.78a.75.75 0 0 0 1.06 0l3.75-3.75a.75.75 0 0 0-1.06-1.06L8.75 8.44V1.75a.75.75 0 0 0-1.5 0v6.69L4.78 5.97a.75.75 0 0 0-1.06 1.06l3.75 3.75zM3.75 13a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5z"></path>
36+
</svg>
37+
Download Image
38+
</button>
39+
<button class="btn btn-primary mt-2" onclick="downloadPythonCode()">
40+
<svg class="octicon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
41+
<path d="M7.47 10.78a.75.75 0 0 0 1.06 0l3.75-3.75a.75.75 0 0 0-1.06-1.06L8.75 8.44V1.75a.75.75 0 0 0-1.5 0v6.69L4.78 5.97a.75.75 0 0 0-1.06 1.06l3.75 3.75zM3.75 13a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5z"></path>
42+
</svg>
43+
Download Python Code
44+
</button>
45+
</div>
46+
</div>
2947
<div class="qr-container">
3048
<div id="qrcode"></div>
3149
</div>

public/main.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import badger2040
2+
import pngdec
3+
import time, os
4+
5+
display = badger2040.Badger2040()
6+
png = pngdec.PNG(display.display)
7+
8+
display.led(128)
9+
display.clear()
10+
11+
try:
12+
png.open_file("badge.png")
13+
png.decode()
14+
except (OSError, RuntimeError):
15+
print("Badge background error")
16+
17+
display.update()

public/script.js

Lines changed: 159 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function loadInitialData() {
3535

3636
// Update full string after setting initial values
3737
updateFullString();
38+
drawBadge(); // Add this line
3839
}
3940

4041
// Call loadInitialData when the page loads
@@ -43,6 +44,12 @@ window.addEventListener('load', loadInitialData);
4344
const inputs = document.querySelectorAll('input:not(#fullstring)');
4445
const fullStringInput = document.getElementById('fullstring');
4546
const qrcodeContainer = document.getElementById('qrcode');
47+
const canvas = document.getElementById('badgeCanvas');
48+
const ctx = canvas.getContext('2d');
49+
50+
// Add this near the top of the file with other constants
51+
const backgroundImage = new Image();
52+
backgroundImage.src = './back.png';
4653

4754
function updateFullString() {
4855
const id = '01234567890'; // Static ID for this example
@@ -66,6 +73,7 @@ function updateFullString() {
6673
// Join the values and create the full string
6774
fullStringInput.value = `${id}iD^${orderedValues.join('^')}^`;
6875
generateQRCode();
76+
drawBadge(); // Add this line
6977
}
7078

7179
function generateQRCode() {
@@ -97,13 +105,18 @@ async function fetchGitHubUser(username) {
97105
}
98106
}
99107

108+
// Add this helper function before updateFormWithGitHubData
109+
function cleanJobTitle(title) {
110+
return title.replace(/\s*@\w+$/, '').trim();
111+
}
112+
100113
// Function to update form fields with GitHub data
101114
function updateFormWithGitHubData(data) {
102115
if (data) {
103116
document.getElementById('firstname').value = data.name ? data.name.split(' ')[0] : '';
104117
document.getElementById('lastname').value = data.name ? data.name.split(' ').slice(1).join(' ') : '';
105118
document.getElementById('company').value = data.company ? data.company.replace(/^@/, '') : '';
106-
document.getElementById('jobtitle').value = data.bio ? data.bio.split('.')[0].trim() : '';
119+
document.getElementById('jobtitle').value = data.bio ? cleanJobTitle(data.bio.split('.')[0].trim()) : '';
107120
updateFullString(); // Update the full string with new data
108121
}
109122
}
@@ -122,8 +135,14 @@ const githubHandleInput = document.getElementById('githubhandle');
122135
githubHandleInput.addEventListener('input', handleGitHubInput);
123136
githubHandleInput.addEventListener('blur', handleGitHubInput);
124137

138+
// Modify the input event listeners to clean job titles
125139
inputs.forEach(input => {
126-
input.addEventListener('input', updateFullString);
140+
input.addEventListener('input', (e) => {
141+
if (e.target.id === 'jobtitle') {
142+
e.target.value = cleanJobTitle(e.target.value);
143+
}
144+
updateFullString();
145+
});
127146
});
128147

129148
updateFullString(); // Initial generation
@@ -166,3 +185,141 @@ fullStringInput.addEventListener('keydown', (event) => {
166185
updateFieldsFromFullString();
167186
}
168187
});
188+
189+
// Modify the drawBadge function
190+
function drawBadge() {
191+
// Enable crisp font rendering
192+
ctx.textRendering = 'optimizeLegibility';
193+
ctx.imageSmoothingEnabled = false;
194+
ctx.antialias = 'none';
195+
196+
const bottomMargin = 10;
197+
const leftMargin = 10;
198+
const topMargin = 10;
199+
200+
ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);
201+
ctx.textBaseline = 'top';
202+
203+
// Draw first name in bold
204+
ctx.font = 'bold 32px "Mona Sans"';
205+
ctx.fillStyle = '#000000';
206+
const firstname = document.getElementById('firstname').value;
207+
ctx.fillText(firstname, leftMargin, topMargin);
208+
209+
// Draw last name in bold
210+
ctx.font = 'bold 24px "Mona Sans"';
211+
const lastname = document.getElementById('lastname').value;
212+
ctx.fillText(lastname, leftMargin, 45);
213+
214+
// Calculate dynamic font size for job title
215+
const jobtitle = document.getElementById('jobtitle').value;
216+
let jobtitleFontSize = 14;
217+
ctx.font = `${jobtitleFontSize}px "Mona Sans"`;
218+
while (ctx.measureText(jobtitle).width > canvas.width - 40 && jobtitleFontSize > 8) {
219+
jobtitleFontSize--;
220+
ctx.font = `${jobtitleFontSize}px "Mona Sans"`;
221+
}
222+
223+
// Get GitHub handle
224+
let githubhandle = document.getElementById('githubhandle').value;
225+
if (githubhandle && !githubhandle.startsWith('@')) {
226+
githubhandle = '@' + githubhandle;
227+
}
228+
229+
// Calculate text metrics to fit job title within canvas
230+
ctx.font = `${jobtitleFontSize}px "Mona Sans"`;
231+
const fontMetrics = ctx.measureText(jobtitle);
232+
const textHeight = fontMetrics.actualBoundingBoxAscent + fontMetrics.actualBoundingBoxDescent;
233+
const lineHeightGap = textHeight * 0.5;
234+
235+
const githubHandleY = (canvas.height) - bottomMargin - textHeight;
236+
const jobTitleY = githubHandleY - textHeight - lineHeightGap;
237+
238+
// Draw the text
239+
ctx.fillText(jobtitle, leftMargin, jobTitleY);
240+
ctx.fillText(githubhandle, leftMargin, githubHandleY);
241+
}
242+
243+
// Replace the font loading with combined font and image loading
244+
Promise.all([
245+
document.fonts.ready,
246+
new Promise(resolve => backgroundImage.onload = resolve)
247+
]).then(() => {
248+
drawBadge();
249+
});
250+
251+
// Update the event listener to handle resize
252+
window.addEventListener('resize', drawBadge);
253+
254+
// Add this function to convert image data to 2-bit black and white
255+
function convertTo2BitBW(imageData) {
256+
const threshold = 128;
257+
const newCanvas = document.createElement('canvas');
258+
newCanvas.width = imageData.width;
259+
newCanvas.height = imageData.height;
260+
const ctx = newCanvas.getContext('2d');
261+
262+
// Create new imageData
263+
const newImageData = ctx.createImageData(imageData.width, imageData.height);
264+
265+
for (let i = 0; i < imageData.data.length; i += 4) {
266+
// Convert to grayscale first
267+
const gray = (imageData.data[i] * 0.299 +
268+
imageData.data[i + 1] * 0.587 +
269+
imageData.data[i + 2] * 0.114);
270+
271+
// Convert to black or white (2-bit)
272+
const bw = gray < threshold ? 0 : 255;
273+
274+
newImageData.data[i] = bw; // R
275+
newImageData.data[i + 1] = bw; // G
276+
newImageData.data[i + 2] = bw; // B
277+
newImageData.data[i + 3] = 255;// A
278+
}
279+
280+
ctx.putImageData(newImageData, 0, 0);
281+
return newCanvas;
282+
}
283+
284+
// Modify the downloadBadge function to use the conversion
285+
function downloadBadge() {
286+
const canvas = document.getElementById('badgeCanvas');
287+
const ctx = canvas.getContext('2d');
288+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
289+
290+
// Convert to 2-bit black and white
291+
const bwCanvas = convertTo2BitBW(imageData);
292+
293+
const link = document.createElement('a');
294+
link.download = 'badge.png';
295+
link.href = bwCanvas.toDataURL('image/png');
296+
link.click();
297+
}
298+
299+
// Add this function at the end of the file
300+
function downloadPythonCode() {
301+
const pythonCode = `import badger2040
302+
import pngdec
303+
import time, os
304+
305+
display = badger2040.Badger2040()
306+
png = pngdec.PNG(display.display)
307+
308+
display.led(128)
309+
display.clear()
310+
311+
try:
312+
png.open_file("badge.png")
313+
png.decode()
314+
except (OSError, RuntimeError):
315+
print("Badge background error")
316+
317+
display.update()`;
318+
319+
const blob = new Blob([pythonCode], { type: 'text/plain' });
320+
const link = document.createElement('a');
321+
link.download = 'main.py';
322+
link.href = URL.createObjectURL(blob);
323+
link.click();
324+
URL.revokeObjectURL(link.href);
325+
}

public/styles.css

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1+
@font-face {
2+
font-family: 'Mona Sans';
3+
src:
4+
url('./Mona-Sans.woff2') format('woff2 supports variations'),
5+
url('./Mona-Sans.woff2') format('woff2-variations');
6+
font-weight: 200 900;
7+
font-stretch: 75% 125%;
8+
}
9+
110
body {
2-
font-family: Arial, sans-serif;
11+
font-family: 'Mona Sans', Arial, sans-serif;
312
margin: 0;
413
padding: 20px;
514
background-color: #f0f0f0;
@@ -24,13 +33,22 @@ body {
2433
flex-direction: column;
2534
align-items: center;
2635
background-color: #f9f9f9;
36+
gap: 20px;
37+
justify-content: center;
2738
}
2839
.qr-container {
2940
margin-bottom: 10px;
3041
}
3142
.input-container {
3243
width: 100%;
3344
}
45+
.badge-preview {
46+
width: 592px;
47+
height: 256px;
48+
}
49+
.badge-preview-buttons {
50+
text-align: center;
51+
}
3452
h1 {
3553
margin-top: 0;
3654
color: #333;
@@ -57,3 +75,9 @@ input {
5775
width: 100%;
5876
box-sizing: border-box;
5977
}
78+
#badgeCanvas {
79+
background-color: #f0f0f0;
80+
border: 1px solid #ddd;
81+
margin: 0 auto;
82+
display: block;
83+
}

0 commit comments

Comments
 (0)