diff --git a/README.rst b/README.rst
index 11f8ef01f..48e733349 100644
--- a/README.rst
+++ b/README.rst
@@ -199,6 +199,7 @@ OpenSlides uses the following projects or parts of them:
* `angular-gettext `_, License: MIT
* `angular-loading-bar `_, License: MIT
* `angular-messages `_, License: MIT
+ * `pdfmake `_, License: MIT
* `angular-pdf `_, License: MIT
* `angular-sanitize `_, License: MIT
* `angular-scroll-glue `_, License: MIT
diff --git a/bower.json b/bower.json
index 58777efe6..58e85c011 100644
--- a/bower.json
+++ b/bower.json
@@ -29,12 +29,16 @@
"ng-dialog": "~0.5.6",
"ng-file-upload": "~11.2.3",
"ngBootbox": "~0.1.3",
+ "pdfmake": "~0.1.17",
"open-sans-fontface": "https://github.com/OpenSlides/open-sans.git#1.4.2.post1",
"roboto-condensed": "~0.3.0",
"tinymce-dist": "4.3.12",
"tinymce-i18n": "OpenSlides/tinymce-i18n#a186ad61e0aa30fdf657e88f405f966d790f0805"
},
"overrides": {
+ "pdfmake-dist": {
+ "main": "build/pdfmake.min.js"
+ },
"pdfjs-dist": {
"main": "build/pdf.combined.js"
},
diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js
index 07bf1811e..789fc43db 100644
--- a/openslides/core/static/js/core/site.js
+++ b/openslides/core/static/js/core/site.js
@@ -18,6 +18,403 @@ angular.module('OpenSlidesApp.core.site', [
'ui.tinymce',
'luegg.directives',
])
+.factory('PdfMakeDocumentProvider', function() {
+ /**
+ * Provides the global Document
+ * @constructor
+ * @param {object} contentProvider - Object with on method `getContent`, which returns an array for content
+ * @param {string} defaultFont - Default font for the document
+ */
+ var createInstance = function(contentProvider, defaultFont) {
+ /**
+ * Generates header for PDF
+ * @constructor
+ */
+ var header = function() {
+ var date = new Date();
+ return {
+ // alignment: 'center',
+ color: '#555',
+ fontSize: 10,
+ margin: [80, 50, 80, 0], //margin: [left, top, right, bottom]
+ columns: ['OpenSlides | Presentation and assembly system', {
+ fontSize: 6,
+ text: 'Stand: ' + date.toLocaleDateString() + " " + date.toLocaleTimeString(),
+ alignment: 'right'
+ }]
+ };
+ },
+ /**
+ * Generates footer line
+ * @function
+ * @param {object} currentPage - An object representing the current page
+ * @param {number} pageCount - number for pages
+ */
+ footer = function(currentPage, pageCount) {
+ return {
+ alignment: 'center',
+ fontSize: 8,
+ color: '#555',
+ text: "Seite: " + currentPage.toString()
+ };
+ },
+ /**
+ * Generates the document(definition) for pdfMake
+ * @function
+ */
+ getDocument = function() {
+ var content = contentProvider.getContent();
+ return {
+ pageSize: 'A4',
+ pageMargins: [80, 90, 80, 60],
+ defaultStyle: {
+ font: defaultFont
+ },
+ fontSize: 8,
+ header: header,
+ footer: footer,
+ content: content,
+ };
+ };
+ return {
+ getDocument: getDocument
+ };
+ };
+ return {
+ createInstance: createInstance
+ };
+ })
+.factory('PdfMakeConverter', function() {
+ /**
+ * Converter component for HTML->JSON for pdfMake
+ * @constructor
+ * @param {object} images - Key-Value structure representing image.src/BASE64 of images
+ * @param {object} fonts - Key-Value structure representing fonts (detailed description below)
+ * @param {object} pdfMake - the converter component enhances pdfMake
+ */
+ var createInstance = function(images, fonts, pdfMake) {
+ var slice = Function.prototype.call.bind([].slice),
+ map = Function.prototype.call.bind([].map),
+ /**
+ * Adds a custom font to pdfMake.vfs
+ * @function
+ * @param {object} fontFiles - object with Files to add to pdfMake.vfs
+ * {
+ * normal: $Filename
+ * bold: $Filename
+ * italics: $Filename
+ * bolditalics: $Filename
+ * }
+ */
+ addFontToVfs = function(fontFiles) {
+ Object.keys(fontFiles).forEach(function(name) {
+ var file = fontFiles[name];
+ pdfMake.vfs[file.name] = file.content;
+ });
+ },
+ /**
+ * Adds custom fonts to pdfMake
+ * @function
+ * @param {object} fontInfo - Font configuration from Backend
+ * {
+ * $FontName : {
+ * normal: $Filename
+ * bold: $Filename
+ * italics: $Filename
+ * bolditalics: $Filename
+ * }
+ * }
+ */
+ registerFont = function(fontInfo) {
+ Object.keys(fontInfo).forEach(function(name) {
+ var font = fontInfo[name];
+ addFontToVfs(font);
+ pdfMake.fonts = pdfMake.fonts || {};
+ pdfMake.fonts[name] = Object.keys(font).reduce(function(fontDefinition, style) {
+ fontDefinition[style] = font[style].name;
+ return fontDefinition;
+ }, {});
+ });
+ },
+ /**
+ * Convertes HTML for use with pdfMake
+ * @function
+ * @param {object} html - html
+ */
+ convertHTML = function(html) {
+ var elementStyles = {
+ "b": ["font-weight:bold"],
+ "strong": ["font-weight:bold"],
+ "u": ["text-decoration:underline"],
+ "em": ["font-style:italic"],
+ "i": ["font-style:italic"],
+ "h1": ["font-size:30"],
+ "h2": ["font-size:28"],
+ "h3": ["font-size:26"],
+ "h4": ["font-size:24"],
+ "h5": ["font-size:22"],
+ "h6": ["font-size:20"],
+ "a": ["color:blue", "text-decoration:underline"]
+ },
+ /**
+ * Parses Children of the current paragraph
+ * @function
+ * @param {object} converted -
+ * @param {object} element -
+ * @param {object} currentParagraph -
+ * @param {object} styles -
+ */
+ parseChildren = function(converted, element, currentParagraph, styles) {
+ var elements = [];
+ var children = element.childNodes;
+ if (children.length !== 0) {
+ _.forEach(children, function(child) {
+ currentParagraph = ParseElement(elements, child, currentParagraph, styles);
+ });
+ }
+ if (elements.length !== 0) {
+ _.forEach(elements, function(el) {
+ converted.push(el);
+ });
+ }
+ return currentParagraph;
+ },
+ /**
+ * Extracts the style from an object
+ * @function
+ * @param {object} o - the current object
+ * @param {object} styles - an array with styles
+ */
+ ComputeStyle = function(o, styles) {
+ styles.forEach(function(singleStyle) {
+ var styleDefinition = singleStyle.trim().toLowerCase().split(":");
+ var style = styleDefinition[0];
+ var value = styleDefinition[1];
+ if (styleDefinition.length == 2) {
+ switch (style) {
+ case "padding-left":
+ o.margin = [parseInt(value), 0, 0, 0];
+ break;
+ case "font-size":
+ o.fontSize = parseInt(value);
+ break;
+ case "text-align":
+ switch (value) {
+ case "right":
+ case "center":
+ case "justify":
+ o.alignment = value;
+ break;
+ }
+ break;
+ case "font-weight":
+ switch (value) {
+ case "bold":
+ o.bold = true;
+ break;
+ }
+ break;
+ case "text-decoration":
+ switch (value) {
+ case "underline":
+ o.decoration = "underline";
+ break;
+ case "line-through":
+ o.decoration = "lineThrough";
+ break;
+ }
+ break;
+ case "font-style":
+ switch (value) {
+ case "italic":
+ o.italics = true;
+ break;
+ }
+ break;
+ case "color":
+ o.color = value;
+ break;
+ case "background-color":
+ o.background = value;
+ break;
+ }
+ }
+ });
+ },
+ /**
+ * Parses a single HTML element
+ * @function
+ * @param {object} alreadyConverted -
+ * @param {object} element -
+ * @param {object} currentParagraph -
+ * @param {object} styles -
+ */
+ ParseElement = function(alreadyConverted, element, currentParagraph, styles) {
+ styles = styles || [];
+ if (element.getAttribute) {
+ var nodeStyle = element.getAttribute("style");
+ if (nodeStyle) {
+ nodeStyle.split(";").forEach(function(nodeStyle) {
+ var tmp = nodeStyle.replace(/\s/g, '');
+ styles.push(tmp);
+ });
+ }
+ }
+ var nodeName = element.nodeName.toLowerCase();
+ switch (nodeName) {
+ case "h1":
+ case "h2":
+ case "h3":
+ case "h4":
+ case "h5":
+ case "h6":
+ currentParagraph = create("text");
+ /* falls through */
+ case "a":
+ parseChildren(alreadyConverted, element, currentParagraph, styles.concat(elementStyles[nodeName]));
+ alreadyConverted.push(currentParagraph);
+ break;
+ case "b":
+ case "strong":
+ case "u":
+ case "em":
+ case "i":
+ parseChildren(alreadyConverted, element, currentParagraph, styles.concat(elementStyles[nodeName]));
+ break;
+ case "table":
+ var t = create("table", {
+ widths: [],
+ body: []
+ });
+ var border = element.getAttribute("border");
+ var isBorder = false;
+ if (border)
+ if (parseInt(border) == 1) isBorder = true;
+ if (!isBorder) t.layout = 'noBorders';
+ parseChildren(t.table.body, element, currentParagraph, styles);
+ var widths = element.getAttribute("widths");
+ if (!widths) {
+ if (t.table.body.length !== 0) {
+ if (t.table.body[0].length !== 0)
+ for (var k = 0; k < t.table.body[0].length; k++)
+ t.table.widths.push("*");
+ }
+ } else {
+ var w = widths.split(",");
+ for (var ko = 0; ko < w.length; ko++) t.table.widths.push(w[ko]);
+ }
+ alreadyConverted.push(t);
+ break;
+ case "tbody":
+ parseChildren(alreadyConverted, element, currentParagraph, styles);
+ break;
+ case "tr":
+ var row = [];
+ parseChildren(row, element, currentParagraph, styles);
+ alreadyConverted.push(row);
+ break;
+ case "td":
+ currentParagraph = create("text");
+ var st = create("stack");
+ st.stack.push(currentParagraph);
+ var rspan = element.getAttribute("rowspan");
+ if (rspan)
+ st.rowSpan = parseInt(rspan);
+ var cspan = element.getAttribute("colspan");
+ if (cspan)
+ st.colSpan = parseInt(cspan);
+ parseChildren(st.stack, element, currentParagraph, styles);
+ alreadyConverted.push(st);
+ break;
+ case "span":
+ parseChildren(alreadyConverted, element, currentParagraph, styles);
+ break;
+ case "br":
+ currentParagraph = create("text");
+ alreadyConverted.push(currentParagraph);
+ break;
+ case "li":
+ case "div":
+ case "p":
+ currentParagraph = create("text");
+ var stack = create("stack");
+ stack.stack.push(currentParagraph);
+ ComputeStyle(stack, styles);
+ parseChildren(stack.stack, element, currentParagraph);
+ alreadyConverted.push(stack);
+ break;
+ case "img":
+ alreadyConverted.push({
+ image: BaseMap[element.getAttribute("src")],
+ width: parseInt(element.getAttribute("width")),
+ height: parseInt(element.getAttribute("height"))
+ });
+ break;
+ case "ul":
+ var u = create("ul");
+ parseChildren(u.ul, element, currentParagraph, styles);
+ alreadyConverted.push(u);
+ break;
+ case "ol":
+ var o = create("ol");
+ parseChildren(o.ol, element, currentParagraph, styles);
+ alreadyConverted.push(o);
+ break;
+ default:
+ var temporary = create("text", element.textContent.replace(/\n/g, ""));
+ if (styles)
+ ComputeStyle(temporary, styles);
+ currentParagraph.text.push(temporary);
+ break;
+ }
+ return currentParagraph;
+ },
+ /**
+ * Parses HTML
+ * @function
+ * @param {string} converted -
+ * @param {object} htmlText -
+ */
+ ParseHtml = function(converted, htmlText) {
+ var html = $(htmlText.replace(/\t/g, "").replace(/\n/g, ""));
+ var emptyParagraph = create("text");
+ slice(html).forEach(function(element) {
+ ParseElement(converted, element, emptyParagraph);
+ });
+ },
+ content = [];
+ ParseHtml(content, html);
+ return content;
+ },
+ BaseMap = images,
+ /**
+ * Creates containerelements for pdfMake
+ * e.g create("text":"MyText") result in { text: "MyText" }
+ * or complex objects create("stack", [{text:"MyText"}, {text:"MyText2"}])
+ *for units / paragraphs of text
+ *
+ * @function
+ * @param {string} name - name of the attribute holding content
+ * @param {object} content - the actual content (maybe empty)
+ */
+ create = function(name, content) {
+ var o = {};
+ content = content || [];
+ o[name] = content;
+ return o;
+ };
+ fonts.forEach(function(fontInfo) {
+ registerFont(fontInfo);
+ });
+ return {
+ convertHTML: convertHTML,
+ createElement: create
+ };
+ };
+ return {
+ createInstance: createInstance
+ };
+})
// Provider to register entries for the main menu.
.provider('mainMenu', [
diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js
index 98fccd042..a9928c9b2 100644
--- a/openslides/motions/static/js/motions/site.js
+++ b/openslides/motions/static/js/motions/site.js
@@ -4,6 +4,207 @@
angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
+.factory('MotionContentProvider', ['gettextCatalog', function(gettextCatalog) {
+ /**
+ * Provides the content as JS objects for Motions in pdfMake context
+ * @constructor
+ */
+ var createInstance = function(converter) {
+ /**
+ * Text of motion
+ * @function
+ * @param {object} motion - Current motion
+ * @param {object} $scope - Current $scope
+ */
+ var textContent = function(motion, $scope) {
+ return converter.convertHTML(motion.getText($scope.version));
+ },
+ /**
+ * Generate text of reason
+ * @function
+ * @param {object} motion - Current motion
+ * @param {object} $scope - Current $scope
+ */
+ reasonContent = function(motion, $scope) {
+ return converter.convertHTML(motion.getReason($scope.version));
+ },
+ /**
+ * Generate header text of motion
+ * @function
+ * @param {object} motion - Current motion
+ * @param {object} $scope - Current $scope
+ */
+ motionHeader = function(motion, $scope) {
+ var header = converter.createElement("text", gettextCatalog.getString("Motion") + " " + motion.identifier + ": " + motion.getTitle($scope.version));
+ header.bold = true;
+ header.fontSize = 26;
+ return header;
+ },
+ /**
+ * Generate text of signment
+ * @function
+ * @param {object} motion - Current motion
+ * @param {object} $scope - Current $scope
+ * @param {object} User - Current user
+ */
+ signment = function(motion, $scope, User) {
+ var label = converter.createElement("text", gettextCatalog.getString('Submitter') + ':\nStatus:');
+ label.width = "30%";
+ label.bold = true;
+ var signment = converter.createElement("stack", [label]);
+ signment.margin = [10, 20, 0, 10];
+ signment.lineHeight = 2.5;
+ return signment;
+ },
+ /**
+ * Generates polls
+ * @function
+ * @param {object} motion - Current motion
+ * @param {object} $scope - Current $scope
+ */
+ polls = function(motion, $scope) {
+ if (!motion.polls.length) return {};
+ var pollLabel = converter.createElement("text", gettextCatalog.getString('Voting result') + ":"),
+ results = function() {
+ return motion.polls.map(function(poll, index) {
+ var id = index + 1,
+ yes = poll.yes,
+ yesRelative = (poll.yes) * 100 / (poll.votescast),
+ no = poll.no,
+ noRelative = (poll.no) * 100 / (poll.votescast),
+ abstain = poll.abstain,
+ abstainRelative = (poll.abstain) * 100 / (poll.votescast),
+ valid = poll.votesvalid,
+ validRelative = (poll.votesvalid) * 100 / (poll.votescast),
+ number = {
+ text: id + ".",
+ width: "5%"
+ },
+ headerText = {
+ text: "Abstimmung",
+ width: "15%"
+ },
+ /**
+ * Generates a part (consisting of different columns) of the polls
+ *
+ * Example Ja 100 ( 90% )
+ *
+ * @function
+ * @param {string} name - E.g. "Ja"
+ * @param {number} value - E.g.100
+ * @param {number} relValue - E.g. 90
+ */
+ createPart = function(name, value, relValue) {
+ var indexColumn = converter.createElement("text");
+ var nameColumn = converter.createElement("text", "" + name);
+ var valueColumn = converter.createElement("text", "" + value);
+ var relColumn = converter.createElement("text", "(" + "" + relValue + "%)");
+ valueColumn.width = "40%";
+ indexColumn.width = "5%";
+ valueColumn.width = "5%";
+ valueColumn.alignment = "right";
+ relColumn.margin = [5, 0, 0, 0];
+ return [indexColumn, nameColumn, valueColumn, relColumn];
+ },
+ yesPart = converter.createElement("columns", createPart(gettextCatalog.getString("Yes"), yes, yesRelative)),
+ noPart = converter.createElement("columns", createPart(gettextCatalog.getString("No"), no, noRelative)),
+ abstainPart = converter.createElement("columns", createPart(gettextCatalog.getString("Abstain"), abstain, abstainRelative)),
+ totalPart = converter.createElement("columns", createPart(gettextCatalog.getString("Valid votes"), valid, validRelative)),
+ heading = converter.createElement("columns", [number, headerText]),
+ pollResult = converter.createElement("stack", [
+ heading, yesPart, noPart, abstainPart, totalPart
+ ]);
+
+ return pollResult;
+ }, {});
+ };
+ pollLabel.width = '35%';
+ pollLabel.bold = true;
+ var result = converter.createElement("columns", [pollLabel, results()]);
+ result.margin = [10, 0, 0, 10];
+ result.lineHeight = 1;
+ return result;
+ },
+ /**
+ * Generates title section for motion
+ * @function
+ * @param {object} motion - Current motion
+ * @param {object} $scope - Current $scope
+ */
+ titleSection = function(motion, $scope) {
+ var title = converter.createElement("text", motion.getTitle($scope.version));
+ title.bold = true;
+ title.fontSize = 14;
+ title.margin = [0, 0, 0, 10];
+ return title;
+ },
+ /**
+ * Generates reason section for polls
+ * @function
+ * @param {object} motion - Current motion
+ * @param {object} $scope - Current $scope
+ */
+ reason = function(motion, $scope) {
+ var r = converter.createElement("text", gettextCatalog.getString("Reason") + ":");
+ r.bold = true;
+ r.fontSize = 14;
+ r.margin = [0, 30, 0, 10];
+ return r;
+ },
+ /**
+ * Generates content as a pdfmake consumable
+ * @function
+ * @param {object} motion - Current motion
+ * @param {object} $scope - Current $scope
+ * @param {object} User - Current user
+ */
+ getContent = function(motion, $scope, User) {
+ return [
+ motionHeader(motion, $scope),
+ signment(motion, $scope, User),
+ polls(motion, $scope),
+ titleSection(motion, $scope),
+ textContent(motion, $scope),
+ reason(motion, $scope),
+ reasonContent(motion, $scope)
+ ];
+ };
+ return {
+ getContent: getContent
+ };
+ };
+ return {
+ createInstance: createInstance
+ };
+}])
+.factory('SingleMotionContentProvider', function() {
+ /**
+ * Generates a content provider
+ * @constructor
+ * @param {object} motionContentProvider - Generates pdfMake structure from motion
+ * @param {object} $scope - Current $scope
+ * @param {object} User - Current User
+ */
+ var createInstance = function(motionContentProvider, motion, $scope, User) {
+ /**
+ * Returns Content for single motion
+ * @function
+ * @param {object} motion - Current motion
+ * @param {object} $scope - Current $scope
+ * @param {object} User - Current User
+ */
+ var getContent = function() {
+ return motionContentProvider.getContent(motion, $scope, User);
+ };
+ return {
+ getContent: getContent
+ };
+ };
+ return {
+ createInstance: createInstance
+ };
+})
+
.config([
'mainMenuProvider',
'gettext',
@@ -573,7 +774,14 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
'User',
'Workflow',
'motion',
- function($scope, $http, ngDialog, MotionForm, Motion, Category, Mediafile, Tag, User, Workflow, motion) {
+ 'SingleMotionContentProvider',
+ 'MotionContentProvider',
+ 'PdfMakeConverter',
+ 'PdfMakeDocumentProvider',
+ function($scope, $http, ngDialog, MotionForm,
+ Motion, Category, Mediafile, Tag,
+ User, Workflow, motion,
+ SingleMotionContentProvider, MotionContentProvider, PdfMakeConverter, PdfMakeDocumentProvider) {
Motion.bindOne(motion.id, $scope, 'motion');
Category.bindAll({}, $scope, 'categories');
Mediafile.bindAll({}, $scope, 'mediafiles');
@@ -584,6 +792,31 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
$scope.version = motion.active_version;
$scope.isCollapsed = true;
+ $scope.makePDF = function(){
+ var content = motion.getText($scope.version) + motion.getReason($scope.version),
+ slice = Function.prototype.call.bind([].slice),
+ map = Function.prototype.call.bind([].map),
+ image_sources = map($(content).find("img"), function(element) {
+ return element.getAttribute("src");
+ });
+
+ $http.post('/motions/encode_media/', JSON.stringify(image_sources)).success(function(data) {
+ /**
+ * Converter for use with pdfMake
+ * @constructor
+ * @param {object} images - An object to resolve the BASE64 encoded images { "$src":$BASE64DATA }
+ * @param {object} fonts - An object representing the available custom fonts
+ * @param {object} pdfMake - pdfMake object for enhancement with custom fonts
+ */
+
+ var converter = PdfMakeConverter.createInstance(data.images, data.fonts, pdfMake),
+ motionContentProvider = MotionContentProvider.createInstance(converter),
+ contentProvider = SingleMotionContentProvider.createInstance(motionContentProvider, motion, $scope, User),
+ documentProvider = PdfMakeDocumentProvider.createInstance(contentProvider, data.defaultFont);
+ pdfMake.createPdf(documentProvider.getDocument()).open();
+ });
+ };
+
// open edit dialog
$scope.openDialog = function (motion) {
ngDialog.open(MotionForm.getDialog(motion));
diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html
index 067a47b94..2061abd98 100644
--- a/openslides/motions/static/templates/motions/motion-detail.html
+++ b/openslides/motions/static/templates/motions/motion-detail.html
@@ -9,6 +9,10 @@
PDF
+
+
+ PDFmake
+
diff --git a/openslides/motions/urls.py b/openslides/motions/urls.py
index 9b37d60d8..f0d89ef51 100644
--- a/openslides/motions/urls.py
+++ b/openslides/motions/urls.py
@@ -14,4 +14,6 @@ urlpatterns = [
url(r'^poll/(?P\d+)/print/$',
views.MotionPollPDF.as_view(),
name='motionpoll_pdf'),
+
+ url(r'^encode_media/', views.encode_media, name="media_encoding")
]
diff --git a/openslides/motions/views.py b/openslides/motions/views.py
index 703558528..e34285a89 100644
--- a/openslides/motions/views.py
+++ b/openslides/motions/views.py
@@ -1,10 +1,16 @@
+import base64
+import json
+import os
+
+from django.conf import settings
from django.db import transaction
-from django.http import Http404
+from django.http import Http404, JsonResponse
from django.utils.text import slugify
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop
from reportlab.platypus import SimpleDocTemplate
from rest_framework import status
+from rest_framework.decorators import api_view
from openslides.core.config import config
from openslides.utils.rest_api import (
@@ -466,3 +472,142 @@ class MotionPDFView(SingleObjectMixin, PDFView):
motions_to_pdf(pdf, motions)
else:
motion_to_pdf(pdf, self.get_object())
+
+
+@api_view(["POST"])
+def encode_media(request):
+ """
+ Encode_image is used in the context of PDF-Generation
+ Takes an array of IMG.src - Paths
+ Retrieves the according images
+ Encodes the images
+ Add configured fonts
+ Puts it into a key-value structure
+
+ {
+ "images": {
+ "media/file/ubuntu.png":"$ENCODED_IMAGE"
+ },
+ "fonts": [{
+ $FontName : {
+ normal: $Filename
+ bold: $Filename
+ italics: $Filename
+ bolditalics: $Filename
+ }
+ }],
+ "default_font": "$DEFAULTFONT"
+ }
+
+ :param request:
+ :return: JsonResponse of the resulting dictionary
+
+ Calling e.g.
+ $.ajax({ type: "POST", url: "/motions/encode_images/",
+ data: JSON.stringify(["$FILEPATH"]),
+ success: function(data){ console.log(data); },
+ dataType: 'application/json' });
+ """
+ body_unicode = request.body.decode('utf-8')
+ file_paths = json.loads(body_unicode)
+ images = {file_path: encode_image_from(file_path) for file_path in file_paths}
+ fonts = encoded_fonts()
+ default_font = get_default_font()
+ return JsonResponse({
+ "images": images,
+ "fonts": fonts,
+ "defaultFont": default_font
+ })
+
+
+def get_default_font():
+ """
+ For development purposes this is hard coded
+ :return: the name of the default Font
+ """
+ return "OpenSans"
+
+
+def encoded_fonts():
+ """
+ Generate font encoding for pdfMake
+ :return: list of Font Encodings
+ """
+
+ fonts = get_configured_fonts()
+
+ enc_fonts = [encode_font(name, files) for name, files in fonts.items()]
+
+ return enc_fonts
+
+
+def get_configured_fonts():
+ """
+ For development purposes, the current font definition is hard coded
+ The form is {
+ $FontName : {
+ normal: $Filename
+ bold: $Filename
+ italics: $Filename
+ bolditalics: $Filename
+ }
+ }
+ This structure is required according to PDFMake specs.
+ :return:
+ """
+
+ fonts = {
+ "OpenSans": {
+ "normal": 'OpenSans-Regular.ttf',
+ "bold": 'OpenSans-Bold.ttf',
+ "italics": 'OpenSans-Italic.ttf',
+ "bolditalics": 'OpenSans-BoldItalic.ttf'
+ }
+ }
+ return fonts
+
+
+def encode_font(fontName, font_files):
+ """
+ Responsible to encode a single font
+ :param fontName: name of the font
+ :param font_files: files for different weighs
+ :return: dictionary with encoded font
+ """
+
+ encoded_files = {type: encode_font_from(file_path) for type, file_path in font_files.items()}
+ return {fontName: encoded_files}
+
+
+def encode_font_from(file_path):
+ """
+ Returns the BASE64 encoded version of an image-file for a given path
+ :param file_path:
+ :return: dictionary with the string representation (content) and the name of the file
+ for the pdfMake.vfs structure
+ """
+ path = os.path.join(settings.SITE_ROOT, 'static/fonts', os.path.basename(file_path))
+ try:
+ with open(path, "rb") as file:
+ string_representation = "{}".format(base64.b64encode(file.read()).decode())
+ except:
+ return ""
+ else:
+ return {"content": string_representation, "name": file_path}
+
+
+def encode_image_from(file_path):
+ """
+ Returns the BASE64 encoded version of an image-file for a given path
+ :param file_path:
+ :return:
+ """
+ path = os.path.join(settings.MEDIA_ROOT, 'file', os.path.basename(file_path))
+ try:
+ with open(path, "rb") as file:
+ string_representation = "data:image/{};base64,{}".format(os.path.splitext(file_path)[1][1:],
+ base64.b64encode(file.read()).decode())
+ except:
+ return ""
+ else:
+ return string_representation