diff --git a/Frontend/.vscode/settings.json b/Frontend/.vscode/settings.json index 20e7007..4d1eeed 100644 --- a/Frontend/.vscode/settings.json +++ b/Frontend/.vscode/settings.json @@ -1,6 +1,7 @@ { "css.styleSheets": [ - "node_moduels/bootstrap/dist/css/bootstrap.min.css", + "node_modules/bootstrap/dist/css/bootstrap.min.css", + "node_modules/bootstrap-icons/font/bootstrap-icons.min.css", "src/styles.css" ] } \ No newline at end of file diff --git a/Frontend/angular.json b/Frontend/angular.json index 305d9d9..b3bcf58 100644 --- a/Frontend/angular.json +++ b/Frontend/angular.json @@ -24,6 +24,11 @@ { "glob": "**/*", "input": "public" + }, + { + "glob": "**/*", + "input": "node_modules/fonteditor-core/woff2", + "output": "woff2" } ], "styles": [ @@ -36,7 +41,9 @@ ], "allowedCommonJsDependencies": [ "fast-xml-parser", - "yamljs" + "yamljs", + "jszip", + "@xmldom/xmldom" ], "server": "src/main.server.ts", "prerender": true, @@ -55,8 +62,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "500kB", - "maximumError": "1MB" + "maximumWarning": "1.5MB", + "maximumError": "2MB" }, { "type": "anyComponentStyle", @@ -115,4 +122,4 @@ "cli": { "analytics": false } -} \ No newline at end of file +} diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 535d983..606e7e1 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -22,6 +22,8 @@ "bootstrap-icons": "^1.11.3", "express": "^4.21.2", "fast-xml-parser": "^4.5.0", + "fonteditor-core": "^2.4.1", + "jszip": "^3.10.1", "marked": "^14.1.3", "prism-code-editor": "^4.0.0-beta.1", "rxjs": "~7.8.0", @@ -4868,6 +4870,15 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -6194,7 +6205,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, "license": "MIT" }, "node_modules/cors": { @@ -7473,6 +7483,14 @@ } } }, + "node_modules/fonteditor-core": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/fonteditor-core/-/fonteditor-core-2.4.1.tgz", + "integrity": "sha512-nKDDt6kBQGq665tQO5tCRQUClJG/2MAF9YT1eKHl+I4NasdSb6DgXrv/gMjNxjo9NyaVEv9KU9VZxLHMstN1wg==", + "dependencies": { + "@xmldom/xmldom": "^0.8.3" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -8146,6 +8164,12 @@ "node": ">=0.10.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", @@ -8467,7 +8491,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, "license": "MIT" }, "node_modules/isbinaryfile": { @@ -8775,6 +8798,48 @@ ], "license": "MIT" }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", @@ -9205,6 +9270,15 @@ } } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10989,6 +11063,12 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11392,7 +11472,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, "license": "MIT" }, "node_modules/promise-inflight": { @@ -12269,6 +12348,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -13429,7 +13514,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/utils-merge": { diff --git a/Frontend/package.json b/Frontend/package.json index d849f6f..f786ef5 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -26,6 +26,8 @@ "bootstrap-icons": "^1.11.3", "express": "^4.21.2", "fast-xml-parser": "^4.5.0", + "fonteditor-core": "^2.4.1", + "jszip": "^3.10.1", "marked": "^14.1.3", "prism-code-editor": "^4.0.0-beta.1", "rxjs": "~7.8.0", diff --git a/Frontend/src/app/app.routes.ts b/Frontend/src/app/app.routes.ts index d842e71..c5bb955 100644 --- a/Frontend/src/app/app.routes.ts +++ b/Frontend/src/app/app.routes.ts @@ -6,6 +6,7 @@ import { Md2htmlComponent } from './md2html/md2html.component'; import { SerializedToolComponent } from './serialized-tool/serialized-tool.component'; import { MssqlScaffolderComponent } from './mssql-scaffolder/mssql-scaffolder.component'; import { DownloadsComponent } from './downloads/downloads.component'; +import { FontConverterComponent } from './font-converter/font-converter.component'; import { IdGeneratorComponent } from './id-generator/id-generator.component'; export const routes: Routes = [ @@ -14,6 +15,7 @@ export const routes: Routes = [ { path: 'md2html', title: "MD to HTML • CodeChef", component: Md2htmlComponent }, { path: 'serialized', title: "Serialized Tool • CodeChef", component: SerializedToolComponent }, { path: 'mssqlscaffold', title: "MSSQL Scaffolder • CodeChef", component: MssqlScaffolderComponent }, + { path: 'fonts', title: "Font Converter • CodeChef", component: FontConverterComponent }, { path: 'id', title: "ID Generator • CodeChef", component: IdGeneratorComponent }, { path: 'downloads', title: "Downloads • CodeChef", component: DownloadsComponent }, diff --git a/Frontend/src/app/app/app.component.ts b/Frontend/src/app/app/app.component.ts index 48e1330..d9a6550 100644 --- a/Frontend/src/app/app/app.component.ts +++ b/Frontend/src/app/app/app.component.ts @@ -19,15 +19,15 @@ export class AppComponent { protected header: boolean = true; constructor( - @Inject(PLATFORM_ID) private platformId: any, + @Inject(PLATFORM_ID) platformId: any, meta: Meta, - private activatedRoute: ActivatedRoute, - private router: Router + activatedRoute: ActivatedRoute, + router: Router ) { - AppComponent.isBrowser = isPlatformBrowser(this.platformId); + AppComponent.isBrowser = isPlatformBrowser(platformId); meta.addTag({ name: "author", content: "IPdotSetAF" }); - this.router.events.pipe( + router.events.pipe( filter(e => e instanceof NavigationEnd), map(() => activatedRoute), map(route => { diff --git a/Frontend/src/app/font-converter/font-converter.component.html b/Frontend/src/app/font-converter/font-converter.component.html new file mode 100644 index 0000000..3c2d888 --- /dev/null +++ b/Frontend/src/app/font-converter/font-converter.component.html @@ -0,0 +1,129 @@ +
+
+

Font Converter

+

Convert fonts to different formats.

+
+ +
+
+

Select or Drag And Drop Font + Files

+
+ +
+
+ + @if (files.length === 0) { +
+ + + Allowed formats: .{{fromExtensions | join: ', .'}} +
+ } + +
+
    + @for (file of files; track file.originalFile.name ; let i = $index) { +
  • +
    +
    + + {{i+1}}. + + {{ file.originalFile.name }} ({{ file.originalFile.size | bytes }}) + @if (file.convertedFile) { + + → Converted to {{ file.convertedFile.name }} ({{ file.convertedFile.size | + bytes }}) + + } + +
    + + @if (file.convertedFile) { + + } +
    +
  • + } +
+
+ +
+
+ + +
+ +
+ +
+
+ + +
+
+ +
+ +
+
+

.TTF (TrueType Font):
TTF is a widely used font format that works on both Windows and + macOS. + It supports high-quality scaling, making it suitable for both screen and print. TTF is commonly used in + applications like word processors, graphic design software, and websites. However, it lacks some + advanced + typographic features compared to newer formats.

+

.OTF (OpenType Font):
OTF is an extension of TTF, offering additional features like + ligatures, alternate characters, and multilingual support. It’s ideal for professional design work, such + as + branding and print materials, due to its advanced typographic capabilities. OTF is also cross-platform + and works + well for both screen and print.

+

.WOFF (Web Open Font Format):
WOFF is specifically designed for web use. It + compresses TTF or + OTF fonts, reducing file size for faster loading on websites. WOFF is supported by most modern browsers + and is a + popular choice for web designers who need efficient, high-quality fonts for online content.

+

.WOFF2:
WOFF2 is an improved version of WOFF, offering even better compression and + faster + loading times. It’s ideal for modern web design, especially when performance and speed are critical. + WOFF2 is + supported by most up-to-date browsers.

+

.EOT (Embedded OpenType):
EOT is a legacy font format designed for web use, primarily + for + older versions of Internet Explorer. It’s less commonly used today due to limited browser support and + the rise + of more efficient formats like WOFF and WOFF2.

+

.SVG (Scalable Vector Graphics):
SVG fonts use vector graphics to render text, making + them + highly scalable and ideal for high-resolution displays. They were historically used for web icons and + simple + text but have largely been replaced by WOFF and WOFF2 for web typography.

+

Each format serves specific purposes, from print and professional design (OTF, TTF) to web optimization + (WOFF, + WOFF2) and legacy support (EOT, SVG). Choosing the right format depends on the intended use and + platform.

+

With this tool, you can easily convert different font formats to each other and in batches! Simply select + your font files, choose the desired output format, and click the "Convert" button. You can then + download the converted files individually or all at once.

+
+
+
\ No newline at end of file diff --git a/Frontend/src/app/font-converter/font-converter.component.spec.ts b/Frontend/src/app/font-converter/font-converter.component.spec.ts new file mode 100644 index 0000000..02f4025 --- /dev/null +++ b/Frontend/src/app/font-converter/font-converter.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FontConverterComponent } from './font-converter.component'; + +describe('FontConverterComponent', () => { + let component: FontConverterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FontConverterComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FontConverterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Frontend/src/app/font-converter/font-converter.component.ts b/Frontend/src/app/font-converter/font-converter.component.ts new file mode 100644 index 0000000..ad256e1 --- /dev/null +++ b/Frontend/src/app/font-converter/font-converter.component.ts @@ -0,0 +1,126 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { Font, woff2 } from 'fonteditor-core'; +import { BytesPipe } from '../../pipes/bytes/bytes.pipe'; +import { JoinPipe } from '../../pipes/join/join.pipe'; +import { downloadFile, downloadFilesAsZip } from '../../utils/download'; +import { Meta } from '@angular/platform-browser'; + +@Component({ + selector: 'app-font-converter', + standalone: true, + imports: [FormsModule, BytesPipe, JoinPipe], + templateUrl: './font-converter.component.html' +}) +export class FontConverterComponent { + protected files: FontFile[] = []; + protected isDragOver: boolean = false; + protected fromExtensions: string[] = ['ttf', 'woff', 'woff2', 'eot', 'svg', 'otf']; + protected toExtensions: string[] = ['ttf', 'woff', 'woff2', 'eot', 'svg']; + protected selectedFormat: string = this.fromExtensions[0]; + protected isConverted: boolean = false; + + protected downloadFile = downloadFile; + + constructor(meta: Meta) { + meta.addTags([ + { name: "description", content: `Converts Fonts from one format to another, supports ${this.fromExtensions.join(', ')} formats, bulk/batch conversion and zip/indevidual download.` }, + { name: "keywords", content: `Font, Converter, Convert, Extension, File, Format, ${this.fromExtensions.join(', ')}, Download, Free, Online, Batch, Bulk, Zip, From, ${this.fromExtensions.map(f => this.fromExtensions.map(t => `${f} to ${t}`).join(', ')).join(', ')}` }, + ]); + } + + protected onFileSelected(event: any): void { + const selectedFiles = event.target.files; + this.addFiles(selectedFiles); + } + + protected onDragOver(event: DragEvent): void { + event.preventDefault(); + this.isDragOver = true; + } + + protected onDragLeave(event: DragEvent): void { + event.preventDefault(); + this.isDragOver = false; + } + + protected onDrop(event: DragEvent): void { + event.preventDefault(); + this.isDragOver = false; + const droppedFiles = event.dataTransfer?.files; + if (droppedFiles) { + this.addFiles(droppedFiles); + } + } + + private addFiles(files: FileList): void { + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (this.isValidFontFile(file)) { + this.files.push({ originalFile: file, convertedFile: null }); + } else { + alert(`File "${file.name}" is not a valid font file.`); + } + } + this.isConverted = false; + } + + protected removeFile(index: number): void { + this.files.splice(index, 1); + } + + private isValidFontFile(file: File): boolean { + const extension = file.name.split('.').pop()!.toLowerCase(); + return this.fromExtensions.includes(extension); + } + + protected onFormatChanged() { + this.isConverted = false; + for (let fileObj of this.files) { + fileObj.convertedFile = null; + } + } + + protected async convert() { + this.isConverted = false; + + await woff2.init('/woff2/woff2.wasm'); + + for (let fileObj of this.files) { + const arrayBuffer = await fileObj.originalFile.arrayBuffer(); + const font = Font.create(arrayBuffer, { + type: fileObj.originalFile.name.split('.').pop() as any, + hinting: true, + kerning: true, + }); + + const buffer = font.write({ + type: this.selectedFormat as any, + toBuffer: true, + hinting: true, + }); + + const blob = new Blob([buffer], { type: this.getMimeType(this.selectedFormat) }); + fileObj.convertedFile = new File([blob], fileObj.originalFile.name.replace(/\.\w+$/, `.${this.selectedFormat}`)); + } + + this.isConverted = true; + } + + private getMimeType(extension: string): string { + if (extension === 'svg') + return 'image/svg+xml'; + if (this.toExtensions.includes(extension)) + return 'font/' + extension; + return 'application/octet-stream'; + } + + protected downloadAll() { + downloadFilesAsZip(this.files.map(file => file.convertedFile!), 'converted-fonts.zip'); + } +} + +interface FontFile { + originalFile: File; + convertedFile?: File | null; +} \ No newline at end of file diff --git a/Frontend/src/app/header/header.component.html b/Frontend/src/app/header/header.component.html index 60b0dab..947f972 100644 --- a/Frontend/src/app/header/header.component.html +++ b/Frontend/src/app/header/header.component.html @@ -33,6 +33,7 @@

CodeChef

  • Markdown to HTML Converter
  • Serialized Tool
  • MSSQL Scaffolder
  • +
  • Font Converter
  • ID Generator
  • diff --git a/Frontend/src/app/home/home.component.html b/Frontend/src/app/home/home.component.html index a12b181..1c53a5b 100644 --- a/Frontend/src/app/home/home.component.html +++ b/Frontend/src/app/home/home.component.html @@ -30,6 +30,7 @@

    CodeChef

    Markdown to HTML Converter Serialized Tool MSSQL Scaffolder + Font Converter ID Generator diff --git a/Frontend/src/pipes/bytes/bytes.pipe.spec.ts b/Frontend/src/pipes/bytes/bytes.pipe.spec.ts new file mode 100644 index 0000000..b1b69c9 --- /dev/null +++ b/Frontend/src/pipes/bytes/bytes.pipe.spec.ts @@ -0,0 +1,8 @@ +import { BytesPipe } from './bytes.pipe'; + +describe('BytesPipe', () => { + it('create an instance', () => { + const pipe = new BytesPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/Frontend/src/pipes/bytes/bytes.pipe.ts b/Frontend/src/pipes/bytes/bytes.pipe.ts new file mode 100644 index 0000000..1dd026a --- /dev/null +++ b/Frontend/src/pipes/bytes/bytes.pipe.ts @@ -0,0 +1,18 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'bytes', + pure: true, + standalone: true +}) +export class BytesPipe implements PipeTransform { + + transform(value: number): string { + if (value === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(value) / Math.log(k)); + return parseFloat((value / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + } + +} diff --git a/Frontend/src/pipes/join/join.pipe.spec.ts b/Frontend/src/pipes/join/join.pipe.spec.ts new file mode 100644 index 0000000..9a5689c --- /dev/null +++ b/Frontend/src/pipes/join/join.pipe.spec.ts @@ -0,0 +1,8 @@ +import { JoinPipe } from './join.pipe'; + +describe('JoinPipe', () => { + it('create an instance', () => { + const pipe = new JoinPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/Frontend/src/pipes/join/join.pipe.ts b/Frontend/src/pipes/join/join.pipe.ts new file mode 100644 index 0000000..ecf3c15 --- /dev/null +++ b/Frontend/src/pipes/join/join.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'join', + pure: true, + standalone: true +}) +export class JoinPipe implements PipeTransform { + + transform(value: any[], delimiter: string): unknown { + return value.join(delimiter); + } + +} diff --git a/Frontend/src/utils/download.ts b/Frontend/src/utils/download.ts new file mode 100644 index 0000000..69561af --- /dev/null +++ b/Frontend/src/utils/download.ts @@ -0,0 +1,17 @@ +import JSZip from "jszip"; + +export async function downloadFilesAsZip(files: File[], fileName: string) { + const zip = new JSZip(); + files.forEach(file => zip.file(file.name, file.arrayBuffer())); + const content = await zip.generateAsync({ type: 'blob' }); + downloadFile(new File([content], 'converted-fonts.zip', { type: 'application/zip' })); +} + +export function downloadFile(file: File): void { + const url = URL.createObjectURL(file); + const link = document.createElement('a'); + link.href = url; + link.download = file.name; + link.click(); + URL.revokeObjectURL(url); +} diff --git a/README.md b/README.md index c177b38..3f8a2fe 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ CodeChef is an online developer tool that aims to eliminate repetitive developer - **Markdown to HTML converter**
    Converts Markdown code to HTML code. - **Serialized tool**
    Converts serialized objects between different formats(JSON, XML, YAML, TOML). - **MSSQL Scaffolder**
    Scaffolds C# Models from MSSQL Tables and Stored Procedures. +- **Font Converter**
    Converts different Font formats to each other(.ttf, .woff, .woff2, .eot, .svg, .otf). - **ID Generator**
    Generates GUID/UUID and NanoIDs with code formating and encoding. - **Need more tools?**
    Open issue/pull request, Everyone is welcome for contribution.