Add PDF custom fonts
This commit is contained in:
parent
209105efc3
commit
e680ca38da
@ -51,8 +51,6 @@
|
||||
"po2json": "^1.0.0-alpha",
|
||||
"rxjs": "^6.3.3",
|
||||
"tinymce": "^4.9.0",
|
||||
"typeface-fira-sans": "^0.0.54",
|
||||
"typeface-fira-sans-condensed": "^0.0.54",
|
||||
"uuid": "^3.3.2",
|
||||
"zone.js": "^0.8.26"
|
||||
},
|
||||
|
@ -51,12 +51,12 @@ export class ConfigService extends OpenSlidesComponent {
|
||||
*
|
||||
* @param key The config value to get from.
|
||||
*/
|
||||
public instant(key: string): any {
|
||||
public instant<T>(key: string): T {
|
||||
const values = this.DS.filter<Config>('core/config', value => value.key === key);
|
||||
if (values.length > 1) {
|
||||
throw new Error('More keys found then expected');
|
||||
} else if (values.length === 1) {
|
||||
return values[0].value;
|
||||
return values[0].value as T;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ export class HttpService {
|
||||
* @param data optional, if sending a data body is required
|
||||
* @param queryParams optional queryparams to append to the path
|
||||
* @param customHeader optional custom HTTP header of required
|
||||
* @param responseType optional response type, default set to json (i.e 'arraybuffer')
|
||||
* @returns a promise containing a generic
|
||||
*/
|
||||
private async send<T>(
|
||||
@ -60,21 +61,26 @@ export class HttpService {
|
||||
method: HTTPMethod,
|
||||
data?: any,
|
||||
queryParams?: QueryParams,
|
||||
customHeader?: HttpHeaders
|
||||
customHeader?: HttpHeaders,
|
||||
responseType?: string
|
||||
): Promise<T> {
|
||||
// end early, if we are in history mode
|
||||
if (this.OSStatus.isInHistoryMode && method !== HTTPMethod.GET) {
|
||||
throw this.handleError('You cannot make changes while in history mode');
|
||||
}
|
||||
|
||||
if (!path.endsWith('/')) {
|
||||
path += '/';
|
||||
// there is a current bug with the responseType.
|
||||
// https://github.com/angular/angular/issues/18586
|
||||
// castting it to 'json' allows the usage of the current array
|
||||
if (!responseType) {
|
||||
responseType = 'json';
|
||||
}
|
||||
|
||||
const url = path + formatQueryParams(queryParams);
|
||||
const options = {
|
||||
body: data,
|
||||
headers: customHeader ? customHeader : this.defaultHeaders
|
||||
headers: customHeader ? customHeader : this.defaultHeaders,
|
||||
responseType: responseType as 'json'
|
||||
};
|
||||
|
||||
try {
|
||||
@ -149,10 +155,17 @@ export class HttpService {
|
||||
* @param data An optional payload for the request.
|
||||
* @param queryParams Optional params appended to the path as the query part of the url.
|
||||
* @param header optional HTTP header if required
|
||||
* @param responseType option expected response type by the request (i.e 'arraybuffer')
|
||||
* @returns A promise holding a generic
|
||||
*/
|
||||
public async get<T>(path: string, data?: any, queryParams?: QueryParams, header?: HttpHeaders): Promise<T> {
|
||||
return await this.send<T>(path, HTTPMethod.GET, data, queryParams, header);
|
||||
public async get<T>(
|
||||
path: string,
|
||||
data?: any,
|
||||
queryParams?: QueryParams,
|
||||
header?: HttpHeaders,
|
||||
responseType?: string
|
||||
): Promise<T> {
|
||||
return await this.send<T>(path, HTTPMethod.GET, data, queryParams, header, responseType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
import * as pdfMake from 'pdfmake/build/pdfmake';
|
||||
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
|
||||
import pdfMake from 'pdfmake/build/pdfmake';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ConfigService } from './config.service';
|
||||
import { HttpService } from './http.service';
|
||||
|
||||
/**
|
||||
* TODO: Images and fonts
|
||||
@ -31,10 +32,84 @@ export class PdfDocumentService {
|
||||
*
|
||||
* @param translate translations
|
||||
* @param configService read config values
|
||||
* @param mediaManageService to read out font files as media data
|
||||
*/
|
||||
public constructor(private translate: TranslateService, private configService: ConfigService) {
|
||||
// It should be possible to add own fonts here
|
||||
pdfMake.vfs = pdfFonts.pdfMake.vfs;
|
||||
public constructor(
|
||||
private translate: TranslateService,
|
||||
private configService: ConfigService,
|
||||
private httpService: HttpService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Define the pdfmake virtual file system for fonts
|
||||
*
|
||||
* @returns the vfs-object
|
||||
*/
|
||||
private async getVfs(): Promise<object> {
|
||||
const fontPathList: string[] = Array.from(
|
||||
// create a list without redundancies
|
||||
new Set(
|
||||
this.configService
|
||||
.instant<string[]>('fonts_available')
|
||||
.map(available => this.configService.instant<any>(available))
|
||||
.map(font => font.path || `/${font.default}`)
|
||||
)
|
||||
);
|
||||
|
||||
const promises = fontPathList.map(fontPath => {
|
||||
return this.convertUrlToBase64(fontPath).then(base64 => {
|
||||
return {
|
||||
[fontPath.split('/').pop()]: base64.split(',')[1]
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const fontDataUrls = await Promise.all(promises);
|
||||
|
||||
let vfs = {};
|
||||
fontDataUrls.map(entry => {
|
||||
vfs = {
|
||||
...vfs,
|
||||
...entry
|
||||
};
|
||||
});
|
||||
|
||||
return vfs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given blob to base64
|
||||
*
|
||||
* @param file File as blob
|
||||
* @returns a promise to the base64 as string
|
||||
*/
|
||||
private async convertUrlToBase64(url: string): Promise<string> {
|
||||
const headers = new HttpHeaders();
|
||||
const file = await this.httpService.get<ArrayBuffer>(url, {}, {}, headers, 'arraybuffer');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(new Blob([file]));
|
||||
reader.onload = () => {
|
||||
const resultStr: string = reader.result as string;
|
||||
resolve(resultStr);
|
||||
};
|
||||
reader.onerror = error => {
|
||||
reject(error);
|
||||
};
|
||||
}) as Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of a font from the value of the given
|
||||
* config variable.
|
||||
*
|
||||
* @param fontType the config variable of the font (font_regular, font_italic)
|
||||
* @returns the name of the selected font
|
||||
*/
|
||||
private getFontName(fontType: string): string {
|
||||
const font = this.configService.instant<any>(fontType);
|
||||
return (font.path || `/${font.default}`).split('/').pop();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,16 +118,25 @@ export class PdfDocumentService {
|
||||
* @param documentContent the content of the pdf as object
|
||||
* @returns the pdf document definition ready to export
|
||||
*/
|
||||
private getStandardPaper(documentContent: object, metadata?: object): object {
|
||||
const standardFontSize = this.configService.instant('general_export_pdf_fontsize');
|
||||
private async getStandardPaper(documentContent: object, metadata?: object): Promise<object> {
|
||||
// define the fonts
|
||||
pdfMake.fonts = {
|
||||
PdfFont: {
|
||||
normal: this.getFontName('font_regular'),
|
||||
bold: this.getFontName('font_bold'),
|
||||
italics: this.getFontName('font_italic'),
|
||||
bolditalics: this.getFontName('font_bold_italic')
|
||||
}
|
||||
};
|
||||
|
||||
pdfMake.vfs = await this.getVfs();
|
||||
|
||||
return {
|
||||
pageSize: 'A4',
|
||||
pageMargins: [75, 90, 75, 75],
|
||||
defaultStyle: {
|
||||
// TODO add fonts to vfs
|
||||
// font: 'PdfFont',
|
||||
fontSize: standardFontSize
|
||||
font: 'PdfFont',
|
||||
fontSize: this.configService.instant('general_export_pdf_fontsize')
|
||||
},
|
||||
header: this.getHeader(),
|
||||
// TODO: option for no footer, wherever this can be defined
|
||||
@ -73,8 +157,8 @@ export class PdfDocumentService {
|
||||
*/
|
||||
private getHeader(): object {
|
||||
// check for the required logos
|
||||
let logoHeaderLeftUrl = this.configService.instant('logo_pdf_header_L').path;
|
||||
let logoHeaderRightUrl = this.configService.instant('logo_pdf_header_R').path;
|
||||
let logoHeaderLeftUrl = this.configService.instant<any>('logo_pdf_header_L').path;
|
||||
let logoHeaderRightUrl = this.configService.instant<any>('logo_pdf_header_R').path;
|
||||
let text;
|
||||
const columns = [];
|
||||
|
||||
@ -151,8 +235,8 @@ export class PdfDocumentService {
|
||||
let logoContainerWidth: string;
|
||||
let pageNumberPosition: string;
|
||||
let logoConteinerSize: Array<number>;
|
||||
let logoFooterLeftUrl = this.configService.instant('logo_pdf_footer_L').path;
|
||||
let logoFooterRightUrl = this.configService.instant('logo_pdf_footer_R').path;
|
||||
let logoFooterLeftUrl = this.configService.instant<any>('logo_pdf_footer_L').path;
|
||||
let logoFooterRightUrl = this.configService.instant<any>('logo_pdf_footer_R').path;
|
||||
|
||||
// if there is a single logo, give it a lot of space
|
||||
if (logoFooterLeftUrl && logoFooterRightUrl) {
|
||||
@ -229,9 +313,9 @@ export class PdfDocumentService {
|
||||
* @param docDefinition the structure of the PDF document
|
||||
*/
|
||||
public download(docDefinition: object, filename: string, metadata?: object): void {
|
||||
pdfMake
|
||||
.createPdf(this.getStandardPaper(docDefinition, metadata))
|
||||
.getBlob(blob => saveAs(blob, `${filename}.pdf`, { autoBOM: true }));
|
||||
this.getStandardPaper(docDefinition, metadata).then(doc => {
|
||||
pdfMake.createPdf(doc).getBlob(blob => saveAs(blob, `${filename}.pdf`, { autoBOM: true }));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,7 +58,7 @@ export class MediaManageService {
|
||||
* @param action determines the action
|
||||
*/
|
||||
public async setAs(file: ViewMediafile, action: string): Promise<void> {
|
||||
const restPath = `rest/core/config/${action}`;
|
||||
const restPath = `rest/core/config/${action}/`;
|
||||
|
||||
const config = this.getMediaConfig(action);
|
||||
const path = config.path !== file.downloadUrl ? file.downloadUrl : '';
|
||||
|
@ -322,7 +322,7 @@ export class MotionPdfService {
|
||||
changes.sort((a, b) => a.getLineFrom() - b.getLineFrom());
|
||||
|
||||
// get the line length from the config
|
||||
const lineLength = this.configService.instant('motions_line_length');
|
||||
const lineLength = this.configService.instant<number>('motions_line_length');
|
||||
|
||||
motionText = this.motionRepo.formatMotion(motion.id, crMode, changes, lineLength);
|
||||
}
|
||||
|
BIN
client/src/assets/fonts/fira-sans-condensed-latin-400.woff
Normal file
BIN
client/src/assets/fonts/fira-sans-condensed-latin-400.woff
Normal file
Binary file not shown.
BIN
client/src/assets/fonts/fira-sans-latin-400.woff
Normal file
BIN
client/src/assets/fonts/fira-sans-latin-400.woff
Normal file
Binary file not shown.
BIN
client/src/assets/fonts/fira-sans-latin-400italic.woff
Normal file
BIN
client/src/assets/fonts/fira-sans-latin-400italic.woff
Normal file
Binary file not shown.
BIN
client/src/assets/fonts/fira-sans-latin-500.woff
Normal file
BIN
client/src/assets/fonts/fira-sans-latin-500.woff
Normal file
Binary file not shown.
BIN
client/src/assets/fonts/fira-sans-latin-500italic.woff
Normal file
BIN
client/src/assets/fonts/fira-sans-latin-500italic.woff
Normal file
Binary file not shown.
22
client/src/assets/styles/font-variables.scss
Normal file
22
client/src/assets/styles/font-variables.scss
Normal file
@ -0,0 +1,22 @@
|
||||
/** Define font variables */
|
||||
|
||||
/** Default Font */
|
||||
$font: 'OSFont';
|
||||
|
||||
$font-regular: url('assets/fonts/fira-sans-latin-400.woff') format('woff');
|
||||
$font-weight-regular: 400;
|
||||
|
||||
$font-italic: url('assets/fonts/fira-sans-latin-400italic.woff') format('woff');
|
||||
$font-weight-italic: 400;
|
||||
|
||||
$font-medium: url('assets/fonts/fira-sans-latin-500.woff') format('woff');
|
||||
$font-weight-medium: 500;
|
||||
|
||||
$font-medium-italic: url('assets/fonts/fira-sans-latin-500italic.woff') format('woff');
|
||||
$font-weight-medium-italic: 500;
|
||||
|
||||
/** Condensed Font */
|
||||
$font-condensed: 'OSFont Condensed';
|
||||
|
||||
$font-condensed-regular: url('assets/fonts/fira-sans-condensed-latin-400.woff') format('woff');
|
||||
$font-weight-condensed-regular: 400;
|
46
client/src/assets/styles/fonts.scss
Normal file
46
client/src/assets/styles/fonts.scss
Normal file
@ -0,0 +1,46 @@
|
||||
@import './font-variables.scss';
|
||||
|
||||
/** Regular */
|
||||
@font-face {
|
||||
font-family: $font;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: $font-weight-regular;
|
||||
src: $font-regular;
|
||||
}
|
||||
|
||||
/** Italic */
|
||||
@font-face {
|
||||
font-family: $font;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
font-weight: $font-weight-italic;
|
||||
src: $font-italic;
|
||||
}
|
||||
|
||||
/** Medium */
|
||||
@font-face {
|
||||
font-family: $font;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: $font-weight-medium;
|
||||
src: $font-medium;
|
||||
}
|
||||
|
||||
/** Medium Italic */
|
||||
@font-face {
|
||||
font-family: $font;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
font-weight: $font-weight-medium-italic;
|
||||
src: $font-medium-italic;
|
||||
}
|
||||
|
||||
/** Condensed Regular */
|
||||
@font-face {
|
||||
font-family: $font-condensed;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: $font-weight-condensed-regular;
|
||||
src: $font-condensed-regular;
|
||||
}
|
@ -4,8 +4,10 @@
|
||||
/** Import brand theme and (new) component themes */
|
||||
@import './assets/styles/openslides-theme.scss';
|
||||
@import './app/site/site.component.scss-theme';
|
||||
@import '../node_modules/typeface-fira-sans/index.css';
|
||||
@import '../node_modules/typeface-fira-sans-condensed/index.css';
|
||||
|
||||
/** fonts */
|
||||
@import './assets/styles/fonts.scss';
|
||||
|
||||
@mixin openslides-components-theme($theme) {
|
||||
@include os-site-theme($theme);
|
||||
/** More components are added here */
|
||||
@ -17,10 +19,10 @@
|
||||
@import '~angular-tree-component/dist/angular-tree-component.css';
|
||||
|
||||
* {
|
||||
font-family: Fira Sans, Roboto, Arial, Helvetica, sans-serif;
|
||||
font-family: OSFont, Fira Sans, Roboto, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.mat-toolbar h2 {
|
||||
font-family: Fira Sans, Roboto, Arial, Helvetica, sans-serif !important;
|
||||
font-family: OSFont, Fira Sans, Roboto, Arial, Helvetica, sans-serif !important;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
@ -40,7 +42,7 @@ h1,
|
||||
h2,
|
||||
h3,
|
||||
.title-font {
|
||||
font-family: Fira Sans Condensed, Roboto-condensed, Arial, Helvetica, sans-serif;
|
||||
font-family: OSFont Condensed, Fira Sans Condensed, Roboto-condensed, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
@ -362,7 +362,7 @@ def get_config_variables():
|
||||
name="font_regular",
|
||||
default_value={
|
||||
"display_name": "Font regular",
|
||||
"default": "static/fonts/Roboto-Regular.woff",
|
||||
"default": "assets/fonts/fira-sans-latin-400.woff",
|
||||
"path": "",
|
||||
},
|
||||
input_type="static",
|
||||
@ -375,7 +375,7 @@ def get_config_variables():
|
||||
name="font_italic",
|
||||
default_value={
|
||||
"display_name": "Font italic",
|
||||
"default": "static/fonts/Roboto-Medium.woff",
|
||||
"default": "assets/fonts/fira-sans-latin-400italic.woff",
|
||||
"path": "",
|
||||
},
|
||||
input_type="static",
|
||||
@ -388,7 +388,7 @@ def get_config_variables():
|
||||
name="font_bold",
|
||||
default_value={
|
||||
"display_name": "Font bold",
|
||||
"default": "static/fonts/Roboto-Condensed-Regular.woff",
|
||||
"default": "assets/fonts/fira-sans-latin-500.woff",
|
||||
"path": "",
|
||||
},
|
||||
input_type="static",
|
||||
@ -401,7 +401,7 @@ def get_config_variables():
|
||||
name="font_bold_italic",
|
||||
default_value={
|
||||
"display_name": "Font bold italic",
|
||||
"default": "static/fonts/Roboto-Condensed-Light.woff",
|
||||
"default": "assets/fonts/fira-sans-latin-500italic.woff",
|
||||
"path": "",
|
||||
},
|
||||
input_type="static",
|
||||
|
45
openslides/core/migrations/0014_changed_default_font.py
Normal file
45
openslides/core/migrations/0014_changed_default_font.py
Normal file
@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
from openslides.core.config import config
|
||||
|
||||
|
||||
def change_font_default_path(apps, schema_editor):
|
||||
"""
|
||||
Writes the new default font path into the database.
|
||||
"""
|
||||
ConfigStore = apps.get_model("core", "ConfigStore")
|
||||
|
||||
try:
|
||||
font_regular = ConfigStore.objects.get(key="font_regular")
|
||||
font_italic = ConfigStore.objects.get(key="font_italic")
|
||||
font_bold = ConfigStore.objects.get(key="font_bold")
|
||||
font_bold_italic = ConfigStore.objects.get(key="font_bold_italic")
|
||||
except ConfigStore.DoesNotExist:
|
||||
return # The key is not in the database, nothing to change here
|
||||
|
||||
default_font_regular = config.config_variables["font_regular"].default_value
|
||||
default_font_italic = config.config_variables["font_italic"].default_value
|
||||
default_font_bold = config.config_variables["font_bold"].default_value
|
||||
default_font_bold_italic = config.config_variables["font_bold_italic"].default_value
|
||||
|
||||
font_regular.value = default_font_regular
|
||||
font_italic.value = default_font_italic
|
||||
font_bold.value = default_font_bold
|
||||
font_bold_italic.value = default_font_bold_italic
|
||||
|
||||
font_regular.save()
|
||||
font_italic.save()
|
||||
font_bold.save()
|
||||
font_bold_italic.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0013_auto_20190119_1641")]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(change_font_default_path),
|
||||
]
|
Loading…
Reference in New Issue
Block a user