diff --git a/client/package.json b/client/package.json index 672baec50..394c88e96 100644 --- a/client/package.json +++ b/client/package.json @@ -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" }, diff --git a/client/src/app/core/services/config.service.ts b/client/src/app/core/services/config.service.ts index eadc39042..51cbb89ac 100644 --- a/client/src/app/core/services/config.service.ts +++ b/client/src/app/core/services/config.service.ts @@ -51,12 +51,12 @@ export class ConfigService extends OpenSlidesComponent { * * @param key The config value to get from. */ - public instant(key: string): any { + public instant(key: string): T { const values = this.DS.filter('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; } diff --git a/client/src/app/core/services/http.service.ts b/client/src/app/core/services/http.service.ts index 53bc23701..658771e4f 100644 --- a/client/src/app/core/services/http.service.ts +++ b/client/src/app/core/services/http.service.ts @@ -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( @@ -60,21 +61,26 @@ export class HttpService { method: HTTPMethod, data?: any, queryParams?: QueryParams, - customHeader?: HttpHeaders + customHeader?: HttpHeaders, + responseType?: string ): Promise { // 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(path: string, data?: any, queryParams?: QueryParams, header?: HttpHeaders): Promise { - return await this.send(path, HTTPMethod.GET, data, queryParams, header); + public async get( + path: string, + data?: any, + queryParams?: QueryParams, + header?: HttpHeaders, + responseType?: string + ): Promise { + return await this.send(path, HTTPMethod.GET, data, queryParams, header, responseType); } /** diff --git a/client/src/app/core/services/pdf-document.service.ts b/client/src/app/core/services/pdf-document.service.ts index 26b8a474e..934d6d221 100644 --- a/client/src/app/core/services/pdf-document.service.ts +++ b/client/src/app/core/services/pdf-document.service.ts @@ -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 { + const fontPathList: string[] = Array.from( + // create a list without redundancies + new Set( + this.configService + .instant('fonts_available') + .map(available => this.configService.instant(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 { + const headers = new HttpHeaders(); + const file = await this.httpService.get(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; + } + + /** + * 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(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 { + // 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('logo_pdf_header_L').path; + let logoHeaderRightUrl = this.configService.instant('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; - 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('logo_pdf_footer_L').path; + let logoFooterRightUrl = this.configService.instant('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 })); + }); } /** diff --git a/client/src/app/site/mediafiles/services/media-manage.service.ts b/client/src/app/site/mediafiles/services/media-manage.service.ts index cd5cad3ff..fd3b7368b 100644 --- a/client/src/app/site/mediafiles/services/media-manage.service.ts +++ b/client/src/app/site/mediafiles/services/media-manage.service.ts @@ -58,7 +58,7 @@ export class MediaManageService { * @param action determines the action */ public async setAs(file: ViewMediafile, action: string): Promise { - 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 : ''; diff --git a/client/src/app/site/motions/services/motion-pdf.service.ts b/client/src/app/site/motions/services/motion-pdf.service.ts index 6efb422cd..6a2ecf0ac 100644 --- a/client/src/app/site/motions/services/motion-pdf.service.ts +++ b/client/src/app/site/motions/services/motion-pdf.service.ts @@ -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('motions_line_length'); motionText = this.motionRepo.formatMotion(motion.id, crMode, changes, lineLength); } diff --git a/client/src/assets/fonts/fira-sans-condensed-latin-400.woff b/client/src/assets/fonts/fira-sans-condensed-latin-400.woff new file mode 100644 index 000000000..290281067 Binary files /dev/null and b/client/src/assets/fonts/fira-sans-condensed-latin-400.woff differ diff --git a/client/src/assets/fonts/fira-sans-latin-400.woff b/client/src/assets/fonts/fira-sans-latin-400.woff new file mode 100644 index 000000000..9c671f401 Binary files /dev/null and b/client/src/assets/fonts/fira-sans-latin-400.woff differ diff --git a/client/src/assets/fonts/fira-sans-latin-400italic.woff b/client/src/assets/fonts/fira-sans-latin-400italic.woff new file mode 100644 index 000000000..7483b7fa2 Binary files /dev/null and b/client/src/assets/fonts/fira-sans-latin-400italic.woff differ diff --git a/client/src/assets/fonts/fira-sans-latin-500.woff b/client/src/assets/fonts/fira-sans-latin-500.woff new file mode 100644 index 000000000..3edb2957b Binary files /dev/null and b/client/src/assets/fonts/fira-sans-latin-500.woff differ diff --git a/client/src/assets/fonts/fira-sans-latin-500italic.woff b/client/src/assets/fonts/fira-sans-latin-500italic.woff new file mode 100644 index 000000000..c3681a327 Binary files /dev/null and b/client/src/assets/fonts/fira-sans-latin-500italic.woff differ diff --git a/client/src/assets/styles/font-variables.scss b/client/src/assets/styles/font-variables.scss new file mode 100644 index 000000000..f799a6f53 --- /dev/null +++ b/client/src/assets/styles/font-variables.scss @@ -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; diff --git a/client/src/assets/styles/fonts.scss b/client/src/assets/styles/fonts.scss new file mode 100644 index 000000000..7dd03d2a3 --- /dev/null +++ b/client/src/assets/styles/fonts.scss @@ -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; +} diff --git a/client/src/styles.scss b/client/src/styles.scss index 3dfc1512d..937d4243c 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -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 { diff --git a/openslides/core/config_variables.py b/openslides/core/config_variables.py index 54887febf..42abf8a5b 100644 --- a/openslides/core/config_variables.py +++ b/openslides/core/config_variables.py @@ -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", diff --git a/openslides/core/migrations/0014_changed_default_font.py b/openslides/core/migrations/0014_changed_default_font.py new file mode 100644 index 000000000..343fe8156 --- /dev/null +++ b/openslides/core/migrations/0014_changed_default_font.py @@ -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), + ]