Skip to content

Commit 42bb7dd

Browse files
authored
feat: Phase 5 - Add Barcode/QR Code Generator and remove deprecated tools
Changes: - Add new BarcodeGenerator tool with support for multiple standards: * QR Code (with error correction levels) * EAN-13 (Retail) * EAN-8 (Small Retail) * Code 128 (High Density) * Code 39 (Alphanumeric) - Add backend BarcodeService with github.com/boombuler/barcode library - Add frontend validation for each barcode standard - Add download button positioned at top-right of result pane - Remove deprecated tools (functionality replaced by TextBasedConverter): * BackslashEscaper * CodeFormatter * ColorConverter * HtmlPreview * MarkdownPreview * QrCodeGenerator - Update TOOL_STATUS.md to mark removed tools as deprecated - Update App.jsx and Sidebar.jsx to reflect removed tools - Update backendBridge.js with new BarcodeService methods
1 parent 7468a99 commit 42bb7dd

24 files changed

Lines changed: 1128 additions & 498 deletions

TOOL_STATUS.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ This document tracks the refactoring and development status of each tool compone
77
- 🟢 **Done** - Tool fully refactored, uses Carbon Design System, follows all guidelines
88
- 🟡 **In Progress** - Tool is being refactored, use caution before modifying
99
- 🔴 **Not Started** - Tool uses legacy patterns, needs full refactoring
10-
-**Deprecated** - Tool functionality replaced by another tool, can be removed
1110

1211
---
1312

@@ -17,18 +16,13 @@ This document tracks the refactoring and development status of each tool compone
1716
|------|--------|-------|--------------|
1817
| JwtDebugger | 🟢 Done | Uses component abstraction system (ToolLayout, ToolTextArea, ToolInputGroup), toggleable layout, consistent button styling with icons (MagicWand, Security, Code), enhanced tabs (custom mode tabs, improved JSON/Claims tabs), resizable textareas with constraints, proper error handling | Completed 2026-01-25 |
1918
| **TextBasedConverter** | 🟢 Done | Unified tool with 45+ algorithms across 5 categories (encrypt, encode, escape, hash, convert). Features: Common Tags (Quick Select), Base64 Image Preview, All Hashes view, Smart ConfigurationPane, 5 Escape methods. Backend: hierarchical structure with 83 comprehensive tests. Phase 2 & 3 complete | Completed 2026-01-31 |
20-
| BackslashEscaper | 🔴 Not Started | Legacy implementation | - |
21-
| CodeFormatter | 🔴 Not Started | Legacy implementation | - |
22-
| ColorConverter | 🔴 Not Started | Legacy implementation | - |
19+
| BarcodeGenerator | 🟢 Done | Multi-standard barcode generator (QR, EAN-13, EAN-8, Code128, Code39). Features: configurable size, error correction levels for QR, client-side validation, download button. | Completed 2026-01-31 |
2320
| CronJobParser | 🔴 Not Started | Legacy implementation | - |
24-
| HtmlPreview | 🔴 Not Started | Legacy implementation | - |
2521
| JsonFormatter | 🔴 Not Started | Legacy implementation | - |
2622
| LineSortDedupe | 🔴 Not Started | Legacy implementation | - |
2723
| LoremIpsumGenerator | 🔴 Not Started | Legacy implementation | - |
28-
| MarkdownPreview | 🔴 Not Started | Legacy implementation | - |
2924
| PhpJsonConverter | 🔴 Not Started | Legacy implementation | - |
3025
| PhpSerializer | 🔴 Not Started | Legacy implementation | - |
31-
| QrCodeGenerator | 🔴 Not Started | Legacy implementation | - |
3226
| RandomStringGenerator | 🔴 Not Started | Legacy implementation | - |
3327
| RegExpTester | 🔴 Not Started | Legacy implementation | - |
3428
| SqlFormatter | 🔴 Not Started | Legacy implementation | - |

barcode_service.go

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/base64"
7+
"fmt"
8+
"image/png"
9+
10+
"github.com/boombuler/barcode"
11+
"github.com/boombuler/barcode/code128"
12+
"github.com/boombuler/barcode/code39"
13+
"github.com/boombuler/barcode/ean"
14+
"github.com/skip2/go-qrcode"
15+
)
16+
17+
type BarcodeService struct {
18+
ctx context.Context
19+
}
20+
21+
func NewBarcodeService() *BarcodeService {
22+
return &BarcodeService{}
23+
}
24+
25+
func (s *BarcodeService) startup(ctx context.Context) {
26+
s.ctx = ctx
27+
}
28+
29+
// GenerateBarcodeRequest represents the request to generate a barcode
30+
type GenerateBarcodeRequest struct {
31+
Content string `json:"content"`
32+
Standard string `json:"standard"` // QR, EAN-13, EAN-8, Code128, Code39
33+
Size int `json:"size"`
34+
Level string `json:"level"` // For QR: L, M, Q, H
35+
Format string `json:"format"` // png, base64
36+
}
37+
38+
// GenerateBarcodeResponse represents the response from barcode generation
39+
type GenerateBarcodeResponse struct {
40+
DataURL string `json:"dataUrl"`
41+
Error string `json:"error"`
42+
}
43+
44+
// GenerateBarcode generates a barcode based on the selected standard
45+
func (s *BarcodeService) GenerateBarcode(req GenerateBarcodeRequest) GenerateBarcodeResponse {
46+
if req.Content == "" {
47+
return GenerateBarcodeResponse{Error: "Content cannot be empty"}
48+
}
49+
50+
// Set defaults
51+
size := req.Size
52+
if size < 64 {
53+
size = 256
54+
}
55+
if size > 1024 {
56+
size = 1024
57+
}
58+
59+
standard := req.Standard
60+
if standard == "" {
61+
standard = "QR"
62+
}
63+
64+
var img barcode.Barcode
65+
var err error
66+
67+
switch standard {
68+
case "QR":
69+
return s.generateQR(req)
70+
case "EAN-13":
71+
// EAN-13 requires 12 or 13 digits
72+
img, err = ean.Encode(req.Content)
73+
case "EAN-8":
74+
// EAN-8 requires 7 or 8 digits
75+
img, err = ean.Encode(req.Content)
76+
case "Code128":
77+
img, err = code128.Encode(req.Content)
78+
case "Code39":
79+
img, err = code39.Encode(req.Content, true, true)
80+
default:
81+
return GenerateBarcodeResponse{Error: fmt.Sprintf("Unsupported barcode standard: %s", standard)}
82+
}
83+
84+
if err != nil {
85+
return GenerateBarcodeResponse{Error: fmt.Sprintf("Failed to encode %s: %v", standard, err)}
86+
}
87+
88+
// Scale the barcode to the requested size
89+
// For 1D barcodes, we maintain aspect ratio
90+
if standard != "QR" {
91+
bounds := img.Bounds()
92+
width := bounds.Dx()
93+
height := bounds.Dy()
94+
95+
// Scale width to size, maintain aspect ratio for height
96+
scaleFactor := float64(size) / float64(width)
97+
newHeight := int(float64(height) * scaleFactor)
98+
99+
// Ensure minimum height for visibility
100+
if newHeight < 100 {
101+
newHeight = 100
102+
}
103+
104+
img, err = barcode.Scale(img, size, newHeight)
105+
if err != nil {
106+
return GenerateBarcodeResponse{Error: fmt.Sprintf("Failed to scale barcode: %v", err)}
107+
}
108+
}
109+
110+
// Encode to PNG
111+
var buf bytes.Buffer
112+
err = png.Encode(&buf, img)
113+
if err != nil {
114+
return GenerateBarcodeResponse{Error: fmt.Sprintf("Failed to encode PNG: %v", err)}
115+
}
116+
117+
// Convert to base64 data URL
118+
base64Data := base64.StdEncoding.EncodeToString(buf.Bytes())
119+
dataURL := fmt.Sprintf("data:image/png;base64,%s", base64Data)
120+
121+
return GenerateBarcodeResponse{DataURL: dataURL}
122+
}
123+
124+
// generateQR generates a QR code using the skip2/go-qrcode library
125+
func (s *BarcodeService) generateQR(req GenerateBarcodeRequest) GenerateBarcodeResponse {
126+
// Parse error correction level
127+
level := qrcode.Medium
128+
switch req.Level {
129+
case "L":
130+
level = qrcode.Low
131+
case "M":
132+
level = qrcode.Medium
133+
case "Q":
134+
level = qrcode.High
135+
case "H":
136+
level = qrcode.Highest
137+
}
138+
139+
// Generate QR code
140+
q, err := qrcode.New(req.Content, level)
141+
if err != nil {
142+
return GenerateBarcodeResponse{Error: fmt.Sprintf("Failed to create QR code: %v", err)}
143+
}
144+
145+
// Generate PNG
146+
png, err := q.PNG(req.Size)
147+
if err != nil {
148+
return GenerateBarcodeResponse{Error: fmt.Sprintf("Failed to generate PNG: %v", err)}
149+
}
150+
151+
// Convert to base64 data URL
152+
base64Data := base64.StdEncoding.EncodeToString(png)
153+
dataURL := fmt.Sprintf("data:image/png;base64,%s", base64Data)
154+
155+
return GenerateBarcodeResponse{DataURL: dataURL}
156+
}
157+
158+
// GetBarcodeStandards returns available barcode standards
159+
func (s *BarcodeService) GetBarcodeStandards() []map[string]string {
160+
return []map[string]string{
161+
{"value": "QR", "label": "QR Code (2D)"},
162+
{"value": "EAN-13", "label": "EAN-13 (Retail - 13 digits)"},
163+
{"value": "EAN-8", "label": "EAN-8 (Small Retail - 8 digits)"},
164+
{"value": "Code128", "label": "Code 128 (High Density)"},
165+
{"value": "Code39", "label": "Code 39 (Alphanumeric)"},
166+
}
167+
}
168+
169+
// GetQRErrorLevels returns available error correction levels for QR codes
170+
func (s *BarcodeService) GetQRErrorLevels() []map[string]string {
171+
return []map[string]string{
172+
{"value": "L", "label": "Low (~7%)"},
173+
{"value": "M", "label": "Medium (~15%)"},
174+
{"value": "Q", "label": "Quartile (~25%)"},
175+
{"value": "H", "label": "High (~30%)"},
176+
}
177+
}
178+
179+
// GetBarcodeSizes returns available barcode sizes
180+
func (s *BarcodeService) GetBarcodeSizes() []map[string]interface{} {
181+
return []map[string]interface{}{
182+
{"value": 128, "label": "Small (128px)"},
183+
{"value": 256, "label": "Medium (256px)"},
184+
{"value": 512, "label": "Large (512px)"},
185+
{"value": 1024, "label": "Extra Large (1024px)"},
186+
}
187+
}
188+
189+
// calculateEANChecksum calculates the EAN checksum digit
190+
func calculateEANChecksum(code string) int {
191+
sum := 0
192+
for i, c := range code {
193+
digit := int(c - '0')
194+
if i%2 == 0 {
195+
sum += digit * 1
196+
} else {
197+
sum += digit * 3
198+
}
199+
}
200+
checksum := (10 - (sum % 10)) % 10
201+
return checksum
202+
}
203+
204+
// ValidateContent validates content for specific barcode standards
205+
func (s *BarcodeService) ValidateContent(content string, standard string) map[string]interface{} {
206+
result := map[string]interface{}{
207+
"valid": true,
208+
"message": "",
209+
}
210+
211+
switch standard {
212+
case "EAN-13":
213+
if len(content) != 12 && len(content) != 13 {
214+
result["valid"] = false
215+
result["message"] = "EAN-13 requires 12 or 13 digits"
216+
} else {
217+
// Check all digits
218+
allDigits := true
219+
for _, c := range content {
220+
if c < '0' || c > '9' {
221+
allDigits = false
222+
break
223+
}
224+
}
225+
if !allDigits {
226+
result["valid"] = false
227+
result["message"] = "EAN-13 can only contain digits"
228+
} else if len(content) == 13 {
229+
// Validate checksum for 13 digits
230+
providedChecksum := int(content[12] - '0')
231+
calculatedChecksum := calculateEANChecksum(content[:12])
232+
if providedChecksum != calculatedChecksum {
233+
result["valid"] = false
234+
result["message"] = fmt.Sprintf("Invalid checksum. Correct EAN-13: %s%d", content[:12], calculatedChecksum)
235+
}
236+
}
237+
}
238+
case "EAN-8":
239+
if len(content) != 7 && len(content) != 8 {
240+
result["valid"] = false
241+
result["message"] = "EAN-8 requires 7 or 8 digits"
242+
} else {
243+
// Check all digits
244+
allDigits := true
245+
for _, c := range content {
246+
if c < '0' || c > '9' {
247+
allDigits = false
248+
break
249+
}
250+
}
251+
if !allDigits {
252+
result["valid"] = false
253+
result["message"] = "EAN-8 can only contain digits"
254+
} else if len(content) == 8 {
255+
// Validate checksum for 8 digits
256+
providedChecksum := int(content[7] - '0')
257+
calculatedChecksum := calculateEANChecksum(content[:7])
258+
if providedChecksum != calculatedChecksum {
259+
result["valid"] = false
260+
result["message"] = fmt.Sprintf("Invalid checksum. Correct EAN-8: %s%d", content[:7], calculatedChecksum)
261+
}
262+
}
263+
}
264+
case "Code39":
265+
// Code 39 supports: 0-9, A-Z, and special characters: - . $ / + % space
266+
validChars := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%"
267+
for _, c := range content {
268+
found := false
269+
for _, valid := range validChars {
270+
if c == valid {
271+
found = true
272+
break
273+
}
274+
}
275+
if !found {
276+
result["valid"] = false
277+
result["message"] = "Code 39 only supports: 0-9, A-Z, and - . $ / + % space"
278+
break
279+
}
280+
}
281+
}
282+
283+
return result
284+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ module dev-toolbox
33
go 1.24.0
44

55
require (
6+
github.com/boombuler/barcode v1.1.0
67
github.com/btcsuite/btcutil v1.0.2
78
github.com/golang-jwt/jwt/v5 v5.3.0
89
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
910
github.com/pelletier/go-toml/v2 v2.2.4
11+
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
1012
github.com/wailsapp/wails/v2 v2.11.0
1113
golang.org/x/crypto v0.47.0
1214
golang.org/x/net v0.49.0

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
22
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
33
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
4+
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
5+
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
46
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
57
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
68
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@@ -76,6 +78,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
7678
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
7779
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
7880
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
81+
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
82+
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
7983
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
8084
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
8185
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=

main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ func main() {
1717
app := NewApp()
1818
jwtService := NewJWTService()
1919
conversionService := NewConversionService()
20+
barcodeService := NewBarcodeService()
2021

2122
// Start HTTP server for Web Mode (port 8081)
2223
go func() {
23-
server := NewServer(jwtService, conversionService)
24+
server := NewServer(jwtService, conversionService, barcodeService)
2425
server.Start(8081)
2526
}()
2627

@@ -37,11 +38,13 @@ func main() {
3738
app.startup(ctx)
3839
jwtService.startup(ctx)
3940
conversionService.startup(ctx)
41+
barcodeService.startup(ctx)
4042
},
4143
Bind: []interface{}{
4244
app,
4345
jwtService,
4446
conversionService,
47+
barcodeService,
4548
},
4649
})
4750

0 commit comments

Comments
 (0)