Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<section class="container">
<p class="description">
This sample ships with <a href="https://fonts.google.com/noto/specimen/Noto+Sans" target="_blank">Noto Sans</a>,
which covers Latin, Cyrillic, and Greek characters. For <strong>Japanese, Chinese, or Korean</strong> support,
download <a href="https://fonts.google.com/noto/specimen/Noto+Sans+JP" target="_blank">Noto Sans JP</a>
(or another CJK font) and upload the <code>.ttf</code> file below.
</p>

@if (builtInFontLoading()) {
<p class="status loading">Loading built-in Noto Sans font…</p>
}

<div class="font-upload">
<label for="fontFile">Upload a custom font (.ttf):</label>
<input
id="fontFile"
type="file"
accept=".ttf,.otf,.woff"
(change)="onFontFileSelected($event)"
/>
@if (uploadedFontName()) {
<span class="file-name">{{ uploadedFontName() }}</span>
}
</div>

<div class="export-buttons">
<button
(click)="exportWithBuiltInFont()"
[disabled]="!canExportBuiltIn()"
>
Export with Noto Sans
</button>
<button
(click)="exportWithUploadedFont()"
[disabled]="!canExportUploaded()"
>
Export with Uploaded Font
</button>
<button
(click)="exportWithDefaultFont()"
[disabled]="isExporting()"
>
Export with Default Font
</button>
</div>

@if (exportStatus()) {
<p class="status">{{ exportStatus() }}</p>
}

<igx-grid #grid [data]="data()" [autoGenerate]="false" height="320px">
<igx-column field="Name" header="Name / Име / 名前"></igx-column>
<igx-column field="City" header="City / Град / 都市"></igx-column>
<igx-column field="Product" header="Product / Продукт / 製品"></igx-column>
<igx-column field="Amount" header="Amount" dataType="number"></igx-column>
</igx-grid>
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
.container {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
}

.description {
color: #555;
max-width: 800px;

a {
color: #0078d4;
text-decoration: none;

&:hover {
text-decoration: underline;
}
}
}

.font-upload {
display: flex;
align-items: center;
gap: 8px;

input[type="file"] {
// Hide the default input
position: absolute;
opacity: 0;
width: 0.1px;
height: 0.1px;
}

label {
font-weight: 500;
padding: 10px 20px;
background-color: #fff;
border: 2px dashed #0078d4;
border-radius: 6px;
cursor: pointer;
color: #0078d4;
transition: all 0.3s ease;
display: inline-block;

&:hover {
background-color: #0078d4;
color: white;
border-style: solid;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3);
}

&:active {
transform: translateY(0);
}
}

.file-name {
color: #28a745;
font-size: 0.9em;
font-weight: 500;
padding: 6px 12px;
background-color: #e8f5e9;
border-radius: 4px;
border: 1px solid #28a745;
}
}

.export-buttons {
display: flex;
flex-wrap: wrap;
gap: 12px;

button {
padding: 8px 16px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
font-size: 14px;

&:not(:disabled):hover {
background-color: #e8e8e8;
}

&:disabled {
opacity: 0.5;
cursor: not-allowed;
}

&:first-child {
background-color: #0078d4;
color: white;
border-color: #0078d4;

&:not(:disabled):hover {
background-color: #106ebe;
}
}
}
}

.status {
padding: 8px 12px;
background-color: #f0f0f0;
border-radius: 4px;
font-size: 0.9em;

&.loading {
color: #0078d4;
font-style: italic;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { ChangeDetectionStrategy, Component, signal, computed, inject, viewChild, OnInit } from '@angular/core';
import { IgxColumnComponent, IgxPdfExporterService, IgxPdfExporterOptions } from 'igniteui-angular/grids/core';
import { IgxGridComponent } from 'igniteui-angular/grids/grid';

/**
* Demonstrates PDF export with a custom Unicode font.
*
* The sample ships with Noto Sans (Latin/Cyrillic/Greek) loaded from
* assets/fonts/noto-sans.json. Users can also upload their own .ttf font —
* for example Noto Sans CJK for Japanese/Chinese/Korean support.
*
* All Noto fonts are licensed under the SIL Open Font License 1.1
* (see assets/fonts/OFL.txt).
*/
@Component({
selector: 'app-export-pdf-custom-font',
templateUrl: './export-pdf-custom-font.component.html',
styleUrls: ['./export-pdf-custom-font.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [IgxGridComponent, IgxColumnComponent]
})
export class ExportPdfCustomFontComponent implements OnInit {
private pdfExporter = inject(IgxPdfExporterService);

protected readonly grid = viewChild.required<IgxGridComponent>('grid');

protected readonly isExporting = signal(false);
protected readonly builtInFontLoaded = signal(false);
protected readonly builtInFontLoading = signal(false);
protected readonly uploadedFontName = signal('');
protected readonly exportStatus = signal('');

// Built-in Noto Sans (Latin / Cyrillic / Greek)
private builtInFontData: string | null = null;
private builtInBoldFontData: string | null = null;

// User-uploaded font (e.g. Noto Sans CJK for Japanese)
private uploadedFontData = signal<string | null>(null);

protected readonly canExportBuiltIn = computed(() => this.builtInFontLoaded() && !this.isExporting());
protected readonly canExportUploaded = computed(() => !!this.uploadedFontData() && !this.isExporting());

protected readonly data = signal([
{ Name: 'Александър Иванов', City: 'София', Product: '商品A', Amount: 1500 },
{ Name: '田中太郎', City: '東京', Product: '商品B', Amount: 2300 },
{ Name: 'Élise Müller', City: 'München', Product: '商品D', Amount: 3200 },
{ Name: '王小明', City: '北京', Product: '商品E', Amount: 1750 },
{ Name: 'Ирина Петрова', City: 'Санкт-Петербург', Product: '製品 F', Amount: 2890 }
]);

public ngOnInit(): void {
this.loadBuiltInFont();
}

/** Loads the built-in Noto Sans font from application assets. */
private async loadBuiltInFont(): Promise<void> {
this.builtInFontLoading.set(true);
this.exportStatus.set('Loading built-in Noto Sans font…');

try {
const response = await fetch('assets/fonts/noto-sans.json');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const fontJson: { normal: string; bold: string } = await response.json();

this.builtInFontData = fontJson.normal;
this.builtInBoldFontData = fontJson.bold;
this.builtInFontLoaded.set(true);
this.exportStatus.set('Noto Sans loaded — ready to export. Upload a CJK font for Japanese/Chinese/Korean support.');
} catch (error) {
console.error('Failed to load built-in font:', error);
this.exportStatus.set('Failed to load built-in Noto Sans font.');
} finally {
this.builtInFontLoading.set(false);
}
}

/** Handles the user uploading a custom .ttf font file. */
protected onFontFileSelected(event: Event): void {
const input = event.target as HTMLInputElement;
if (!input.files?.[0]) {
return;
}
const file = input.files[0];
this.uploadedFontName.set(file.name);
this.exportStatus.set(`Reading "${file.name}"…`);

this.readFontFile(file).then(base64 => {
this.uploadedFontData.set(base64);
this.exportStatus.set(`"${file.name}" loaded — you can now export with the uploaded font.`);
});
}

/** Export using the built-in Noto Sans font. */
protected exportWithBuiltInFont(): void {
if (!this.builtInFontData) {
return;
}

this.isExporting.set(true);
this.exportStatus.set('Exporting PDF with Noto Sans…');

const options = new IgxPdfExporterOptions('NotoSansExport');
options.customFont = {
name: 'NotoSans',
data: this.builtInFontData
};

if (this.builtInBoldFontData) {
options.customFont.bold = {
name: 'NotoSans-Bold',
data: this.builtInBoldFontData
};
}

this.pdfExporter.exportEnded.subscribe({
next: () => {
this.isExporting.set(false);
this.exportStatus.set(
'PDF exported with Noto Sans. Note: CJK characters (Japanese, Chinese, Korean) require a CJK font.'
);
}
});

this.pdfExporter.export(this.grid(), options);
}

/** Export using the user-uploaded font file. */
protected exportWithUploadedFont(): void {
const fontData = this.uploadedFontData();
if (!fontData) {
return;
}

this.isExporting.set(true);
this.exportStatus.set('Exporting PDF with uploaded font…');

const options = new IgxPdfExporterOptions('CustomFontExport');
options.customFont = {
name: 'CustomFont',
data: fontData
};

this.pdfExporter.exportEnded.subscribe({
next: () => {
this.isExporting.set(false);
this.exportStatus.set('PDF exported successfully with the uploaded font!');
}
});

this.pdfExporter.export(this.grid(), options);
}

/** Export with the default PDF font (Helvetica). */
protected exportWithDefaultFont(): void {
this.isExporting.set(true);
this.exportStatus.set('Exporting PDF with default font (Helvetica)…');

const options = new IgxPdfExporterOptions('DefaultFontExport');

this.pdfExporter.exportEnded.subscribe({
next: () => {
this.isExporting.set(false);
this.exportStatus.set(
'PDF exported — non-Latin characters may not render correctly with the default Helvetica font.'
);
}
});

this.pdfExporter.export(this.grid(), options);
}

private readFontFile(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const result = reader.result as string;
const base64 = result.includes(',') ? result.split(',')[1] : result;
resolve(base64);
};
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}
}
1 change: 1 addition & 0 deletions src/app/services/services-routes-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const servicesRoutesData = {
'export-excel-sample-1': { displayName: 'Excel Export Grid', parentName: 'Excel Export' },
'export-excel-tree-grid-sample': { displayName: 'Excel Export TreeGrid', parentName: 'Excel Export' },
'export-pdf': { displayName: 'PDF Export Raw Data', parentName: 'PDF Export' },
'export-pdf-custom-font': { displayName: 'PDF Export with Custom Font', parentName: 'PDF Export' },
'localization-sample-1': { displayName: 'Localize one component', parentName: 'Localization' },
'localization-sample-2': { displayName: 'Localize All', parentName: 'Localization' },
'localization-sample-3': { displayName: 'Localize partially', parentName: 'Localization' },
Expand Down
6 changes: 6 additions & 0 deletions src/app/services/services.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { servicesRoutesData } from './services-routes-data';
import { TransactionBaseComponent } from './transaction/transaction-base/transaction-base.component';
import { PdfExportComponent } from './export-pdf/pdf-export.component';
import { LocalizationAllResourcesComponent } from './localization-samples/localization-all-resources/localization-all-resources.component';
import { ExportPdfCustomFontComponent } from './export-pdf-custom-font/export-pdf-custom-font.component';
// tslint:enable:max-line-length

export const ServicesRoutes: Routes = [
Expand Down Expand Up @@ -47,6 +48,11 @@ export const ServicesRoutes: Routes = [
data: servicesRoutesData['export-pdf'],
path: 'export-pdf'
},
{
component: ExportPdfCustomFontComponent,
data: servicesRoutesData['export-pdf-custom-font'],
path: 'export-pdf-custom-font'
},
{
component: TreeGridExcelExportSample1Component,
data: servicesRoutesData['export-excel-tree-grid-sample'],
Expand Down
Loading
Loading