Updated PDF layout

- use default font for pdf from vfs_font.js
  remove base64 encoding function on server side
- use recommendation config value in motion detail template
This commit is contained in:
Emanuel Schütze 2016-11-02 22:45:43 +01:00
parent 18accc58ae
commit 4e1fdc6b22
12 changed files with 483 additions and 473 deletions

View File

@ -40,7 +40,10 @@
},
"overrides": {
"pdfmake-dist-dist": {
"main": "build/pdfmake.min.js"
"main": [
"build/pdfmake.min.js",
"build/vfs_fonts.js"
]
},
"pdfjs-dist": {
"main": "build/pdf.combined.js"

View File

@ -6,15 +6,15 @@ angular.module('OpenSlidesApp.agenda.pdf', ['OpenSlidesApp.core.pdf'])
.factory('AgendaContentProvider', [
'gettextCatalog',
'PdfPredefinedFunctions',
function(gettextCatalog, PdfPredefinedFunctions) {
'PDFLayout',
function(gettextCatalog, PDFLayout) {
var createInstance = function(items) {
//use the Predefined Functions to create the title
var title = PdfPredefinedFunctions.createTitle(gettextCatalog.getString("Agenda"));
// page title
var title = PDFLayout.createTitle(gettextCatalog.getString("Agenda"));
//function to generate the item list out of the given "items" object
// generate the item list with all subitems
var createItemList = function() {
var agenda_items = [];
angular.forEach(items, function (item) {

View File

@ -315,7 +315,7 @@ angular.module('OpenSlidesApp.agenda.site', [
};
$scope.makePDF = function() {
var filename = gettextCatalog.getString("Agenda")+".pdf";
var filename = gettextCatalog.getString('Agenda') + '.pdf';
var agendaContentProvider = AgendaContentProvider.createInstance($scope.items);
var documentProvider = PdfMakeDocumentProvider.createInstance(agendaContentProvider);
pdfMake.createPdf(documentProvider.getDocument()).download(filename);

View File

@ -6,15 +6,15 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
.factory('AssignmentContentProvider', [
'gettextCatalog',
'PdfPredefinedFunctions',
function(gettextCatalog, PdfPredefinedFunctions) {
'PDFLayout',
function(gettextCatalog, PDFLayout) {
var createInstance = function(assignment) {
//use the Predefined Functions to create the title
var title = PdfPredefinedFunctions.createTitle(assignment.title);
// page title
var title = PDFLayout.createTitle(assignment.title);
//create the preamble
// number of posts
var createPreamble = function() {
var preambleText = gettextCatalog.getString("Number of posts to be elected") + ": ";
var memberNumber = ""+assignment.open_posts;
@ -34,7 +34,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
return preamble;
};
//adds the description if present in the assignment
// description
var createDescription = function() {
if (assignment.description) {
var descriptionText = gettextCatalog.getString("Description") + ":";
@ -56,7 +56,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
}
};
//creates the candidate list in columns if the assignment phase is 'voting'
// show candidate list (if assignment phase is not 'finished')
var createCandidateList = function() {
if (assignment.phase != 2) {
var candidatesText = gettextCatalog.getString("Candidates") + ": ";
@ -96,17 +96,17 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
return {
text: candidateName + "*",
bold: true,
style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length)
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
};
} else {
return {
text: candidateName,
style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length)
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
};
}
};
//creates the pull result table
// creates the election result table
var createPollResultTable = function() {
var resultBody = [];
angular.forEach(assignment.polls, function(poll, pollIndex) {
@ -144,7 +144,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
electedCandidateLine(candidateName, pollOption, pollTableBody),
{
text: votes[0].value + " " + votes[0].percentStr,
style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length)
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
}
]);
} else if (poll.pollmethod == 'yn') {
@ -163,7 +163,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
votes[1].percentStr
}
],
style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length)
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
}
]);
} else if (poll.pollmethod == 'yna') {
@ -187,7 +187,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
votes[2].percentStr
}
],
style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length)
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
}
]);
}
@ -247,7 +247,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
}
});
//Add the legend to the result body
// add the legend to the result body
if (assignment.polls.length > 0) {
resultBody.push({
text: "* = " + gettextCatalog.getString("is elected"),
@ -281,12 +281,12 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
.factory('BallotContentProvider', [
'gettextCatalog',
'PdfPredefinedFunctions',
function(gettextCatalog, PdfPredefinedFunctions) {
'PDFLayout',
function(gettextCatalog, PDFLayout) {
var createInstance = function(scope, poll, pollNumber) {
// use the Predefined Functions to create the title
// page title
var createTitle = function() {
return {
text: scope.assignment.title,
@ -294,7 +294,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
};
};
//function to create the poll hint
// poll description
var createPollHint = function() {
var description = poll.description ? ': ' + poll.description : '';
return {
@ -303,19 +303,19 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
};
};
//function to create the selection entries
// election entries
var createYNBallotEntry = function(decision) {
var YNColumn = [
{
width: "auto",
stack: [
PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("Yes"))
PDFLayout.createBallotEntry(gettextCatalog.getString("Yes"))
]
},
{
width: "auto",
stack: [
PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("No"))
PDFLayout.createBallotEntry(gettextCatalog.getString("No"))
]
},
];
@ -324,7 +324,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
YNColumn.push({
width: "auto",
stack: [
PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("Abstain"))
PDFLayout.createBallotEntry(gettextCatalog.getString("Abstain"))
]
});
}
@ -346,7 +346,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
if (poll.pollmethod == 'votes') {
angular.forEach(poll.options, function(option) {
var candidate = option.candidate.get_full_name();
candidateBallotList.push(PdfPredefinedFunctions.createBallotEntry(candidate));
candidateBallotList.push(PDFLayout.createBallotEntry(candidate));
});
} else {
angular.forEach(poll.options, function(option) {
@ -361,7 +361,6 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
// since it is not possible to give a column a fixed height, we draw an "empty" column
// with a one px width and a fixed top-margin
return {
columns : [
{
@ -431,7 +430,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
widths: ['50%', '50%'],
body: tableBody
},
layout: PdfPredefinedFunctions.getBallotLayoutLines()
layout: PDFLayout.getBallotLayoutLines()
}];
};
@ -451,13 +450,13 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
.factory('AssignmentCatalogContentProvider', [
'gettextCatalog',
'PdfPredefinedFunctions',
'PDFLayout',
'Config',
function(gettextCatalog, PdfPredefinedFunctions, Config) {
function(gettextCatalog, PDFLayout, Config) {
var createInstance = function(allAssignmnets) {
var title = PdfPredefinedFunctions.createTitle(
var title = PDFLayout.createTitle(
gettextCatalog.getString(Config.get('assignments_pdf_title').value)
);
@ -476,7 +475,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
var createTOContent = function(assignmentTitles) {
var heading = {
text: gettextCatalog.getString("Table of contents"),
style: "heading",
style: "heading2",
};
var toc = [];
@ -490,7 +489,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
return [
heading,
toc,
PdfPredefinedFunctions.addPageBreak()
PDFLayout.addPageBreak()
];
};
@ -503,7 +502,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
assignmentTitles.push(assignment.title);
assignmentContent.push(assignment.getContent());
if (key < allAssignmnets.length - 1) {
assignmentContent.push(PdfPredefinedFunctions.addPageBreak());
assignmentContent.push(PDFLayout.addPageBreak());
}
});

View File

@ -4,23 +4,51 @@
angular.module('OpenSlidesApp.core.pdf', [])
.factory('PdfPredefinedFunctions', [
/*
* General layout functions for building PDFs with pdfmake.
*/
.factory('PDFLayout', [
function() {
var PdfPredefinedFunctions = {};
var PDFLayout = {};
var BallotCircleDimensions = {
yDistance: 6,
size: 8
};
PdfPredefinedFunctions.createTitle = function(titleString) {
// Set and return default font family.
//
// To use custom ttf font files you have to replace the vfs_fonts.js file.
// See https://github.com/pdfmake/pdfmake/wiki/Custom-Fonts---client-side
PDFLayout.getFontName = function() {
pdfMake.fonts = {
Roboto: {
normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf',
italics: 'Roboto-Italic.ttf',
bolditalics: 'Roboto-Italic.ttf'
}
};
return "Roboto";
};
// page title
PDFLayout.createTitle = function(title) {
return {
text: titleString,
text: title,
style: "title"
};
};
// function to apply a pagebreak-keyword
PdfPredefinedFunctions.addPageBreak = function() {
// page subtitle
PDFLayout.createSubtitle = function(subtitle) {
return {
text: subtitle,
style: "subtitle"
};
};
// pagebreak
PDFLayout.addPageBreak = function() {
return [
{
text: '',
@ -29,7 +57,8 @@ angular.module('OpenSlidesApp.core.pdf', [])
];
};
PdfPredefinedFunctions.flipTableRowStyle = function(currentTableSize) {
// table row style
PDFLayout.flipTableRowStyle = function(currentTableSize) {
if (currentTableSize % 2 === 0) {
return "tableEven";
} else {
@ -38,7 +67,7 @@ angular.module('OpenSlidesApp.core.pdf', [])
};
// draws a circle
PdfPredefinedFunctions.drawCircle = function(y, size) {
PDFLayout.drawCircle = function(y, size) {
return [
{
type: 'ellipse',
@ -51,14 +80,15 @@ angular.module('OpenSlidesApp.core.pdf', [])
];
};
//Returns an entry in the ballot with a circle to draw into
PdfPredefinedFunctions.createBallotEntry = function(decision) {
// returns an entry in the ballot with a circle to draw into
PDFLayout.createBallotEntry = function(decision) {
return {
margin: [40+BallotCircleDimensions.size, 10, 0, 0],
columns: [
{
width: 15,
canvas: PdfPredefinedFunctions.drawCircle(BallotCircleDimensions.yDistance, BallotCircleDimensions.size)
canvas: PDFLayout.drawCircle(BallotCircleDimensions.yDistance,
BallotCircleDimensions.size)
},
{
width: "auto",
@ -68,7 +98,8 @@ angular.module('OpenSlidesApp.core.pdf', [])
};
};
PdfPredefinedFunctions.getBallotLayoutLines = function() {
// crop marks for ballot papers
PDFLayout.getBallotLayoutLines = function() {
return {
hLineWidth: function(i, node) {
return (i === 0 || i === node.table.body.length) ? 0 : 0.5;
@ -85,10 +116,11 @@ angular.module('OpenSlidesApp.core.pdf', [])
};
};
return PdfPredefinedFunctions;
return PDFLayout;
}
])
.factory('HTMLValidizer', function() {
var HTMLValidizer = {};
@ -111,67 +143,73 @@ angular.module('OpenSlidesApp.core.pdf', [])
return HTMLValidizer;
})
.factory('PdfMakeDocumentProvider', [
'gettextCatalog',
'Config',
function(gettextCatalog, Config) {
'PDFLayout',
function(gettextCatalog, Config, PDFLayout) {
/**
* 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
* Provides the global document
* @constructor
* @param {object} contentProvider - Object with on method `getContent`, which
* returns an array for content
*/
var createInstance = function(contentProvider) {
// PDF header
var header = function() {
var date = new Date();
var columns = [];
// add here your custom logo (which has to be added to a custom vfs_fonts.js)
// see https://github.com/pdfmake/pdfmake/wiki/Custom-Fonts---client-side
/*
columns.push({
image: 'logo.png',
fit: [180,40]
});*/
var line1 = [
Config.get('general_event_name').value,
Config.get('general_event_description').value
].join(' ');
var line2 = [
Config.get('general_event_location').value,
Config.get('general_event_date').value
].join(', ');
var text = [line1, line2].join('\n');
columns.push({
text: text,
fontSize:10,
width: '100%'
});
return {
// alignment: 'center',
color: '#555',
fontSize: 10,
margin: [80, 50, 80, 0], //margin: [left, top, right, bottom]
columns: [
{
text: Config.get('general_event_name').value + ' · ' + Config.get('general_event_description').value ,
fontSize:10,
width: '70%'
},
{
fontSize: 6,
width: '30%',
text: gettextCatalog.getString('As of') + " " + date.toLocaleDateString() + " " + date.toLocaleTimeString(),
alignment: 'right'
}]
fontSize: 9,
margin: [80, 30, 80, 10], // [left, top, right, bottom]
columns: columns,
columnGap: 10
};
},
/**
* Generates footer line
* @function
* @param {object} currentPage - An object representing the current page
* @param {number} pageCount - number for pages
*/
footer = function(currentPage, pageCount) {
};
// PDF footer
var footer = function(currentPage, pageCount) {
return {
alignment: 'center',
fontSize: 8,
color: '#555',
text: gettextCatalog.getString('Page') + ' ' + currentPage.toString() + ' / ' + pageCount.toString()
text: gettextCatalog.getString('Page') + ' ' + currentPage.toString() +
' / ' + pageCount.toString()
};
},
/**
* Generates the document(definition) for pdfMake
* @function
*/
getDocument = function() {
};
// Generates the document(definition) for pdfMake
var getDocument = function() {
var content = contentProvider.getContent();
return {
pageSize: 'A4',
pageMargins: [80, 90, 80, 60],
defaultStyle: {
font: defaultFont,
font: PDFLayout.getFontName(),
fontSize: 10
},
header: header,
@ -179,12 +217,16 @@ angular.module('OpenSlidesApp.core.pdf', [])
content: content,
styles: {
title: {
fontSize: 30,
fontSize: 18,
margin: [0,0,0,20],
bold: true
},
subtitle: {
fontSize: 9,
margin: [0,-20,0,20],
},
preamble: {
fontSize: 12,
fontSize: 10,
margin: [0,0,0,10],
},
userDataTitle: {
@ -196,11 +238,16 @@ angular.module('OpenSlidesApp.core.pdf', [])
fontSize: 11,
margin: [0,7]
},
heading: {
fontSize: 16,
heading2: {
fontSize: 14,
margin: [0,0,0,10],
bold: true
},
heading3: {
fontSize: 12,
margin: [0,10,0,0],
bold: true
},
userDataHeading: {
fontSize: 14,
margin: [0,10],
@ -219,11 +266,11 @@ angular.module('OpenSlidesApp.core.pdf', [])
margin: [0,3]
},
listParent: {
fontSize: 14,
fontSize: 12,
margin: [0,5]
},
listChild: {
fontSize: 11,
fontSize: 10,
margin: [0,5]
},
tableHeader: {
@ -239,10 +286,20 @@ angular.module('OpenSlidesApp.core.pdf', [])
tableConclude: {
fillColor: '#ddd',
bold: true
},
grey: {
fillColor: '#ddd',
},
lightgrey: {
fillColor: '#aaa',
},
bold: {
bold: true,
}
}
};
};
return {
getDocument: getDocument
};
@ -256,14 +313,14 @@ angular.module('OpenSlidesApp.core.pdf', [])
.factory('PdfMakeBallotPaperProvider', [
'gettextCatalog',
'Config',
function(gettextCatalog, Config) {
'PDFLayout',
function(gettextCatalog, Config, PDFLayout) {
/**
* 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) {
var createInstance = function(contentProvider) {
/**
* Generates the document(definition) for pdfMake
* @function
@ -274,7 +331,7 @@ angular.module('OpenSlidesApp.core.pdf', [])
pageSize: 'A4',
pageMargins: [0, 0, 0, 0],
defaultStyle: {
font: defaultFont,
font: PDFLayout.getFontName(),
fontSize: 10
},
content: content,
@ -308,10 +365,9 @@ angular.module('OpenSlidesApp.core.pdf', [])
* 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 createInstance = function(images, pdfMake) {
var slice = Function.prototype.call.bind([].slice),
map = Function.prototype.call.bind([].map),
@ -319,47 +375,6 @@ angular.module('OpenSlidesApp.core.pdf', [])
DIFF_MODE_INSERT = 1,
DIFF_MODE_DELETE = 2,
/**
* 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
@ -726,9 +741,6 @@ angular.module('OpenSlidesApp.core.pdf', [])
o[name] = content;
return o;
};
fonts.forEach(function(fontInfo) {
registerFont(fontInfo);
});
return {
convertHTML: convertHTML,
createElement: create

View File

@ -788,22 +788,12 @@ class MediaEncoder(utils_views.APIView):
Takes an array of IMG.src - Paths
Retrieves the according images
Encodes the images to BASE64
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:
@ -818,86 +808,10 @@ class MediaEncoder(utils_views.APIView):
body_unicode = request.body.decode('utf-8')
file_paths = json.loads(body_unicode)
images = {file_path: self.encode_image_from(file_path) for file_path in file_paths}
fonts = self.encoded_fonts()
default_font = self.get_default_font()
return Response({
"images": images,
"fonts": fonts,
"defaultFont": default_font
"images": images
})
def get_default_font(self):
"""
Returns the default font for pdfMake.
Note: For development purposes this is hard coded.
:return: the name of the default Font
"""
return 'OpenSans'
def encoded_fonts(self):
"""
Generate font encoding for pdfMake
:return: list of Font Encodings
"""
fonts = self.get_configured_fonts()
enc_fonts = [self.encode_font(name, files) for name, files in fonts.items()]
return enc_fonts
def get_configured_fonts(self):
"""
Returns the configured fonts
Note: 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(self, font_name, 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: self.encode_font_from(file_path) for type, file_path in font_files.items()}
return {font_name: encoded_files}
def encode_font_from(self, 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.MODULE_DIR, '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(self, file_path):
"""
Returns the BASE64 encoded version of an image-file for a given path

View File

@ -109,7 +109,7 @@ angular.module('OpenSlidesApp.motions', [
}
// calculate percent value
var config = Config.get('motions_poll_100_percent_base').value;
var percentStr;
var percentStr = '';
var percentNumber = null;
var base = null;
if (!impossible) {
@ -137,7 +137,8 @@ angular.module('OpenSlidesApp.motions', [
return {
'value': value,
'percentStr': percentStr,
'percentNumber': percentNumber
'percentNumber': percentNumber,
'display': value + ' ' + percentStr
};
}
}

View File

@ -30,9 +30,9 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
});
$http.post('/core/encode_media/', JSON.stringify(image_sources)).success(function(data) {
var converter = PdfMakeConverter.createInstance(data.images, data.fonts, pdfMake);
var converter = PdfMakeConverter.createInstance(data.images, pdfMake);
var motionContentProvider = MotionContentProvider.createInstance(converter, $scope.motion, $scope, User, $http);
var documentProvider = PdfMakeDocumentProvider.createInstance(motionContentProvider, data.defaultFont);
var documentProvider = PdfMakeDocumentProvider.createInstance(motionContentProvider);
var filename = gettextCatalog.getString("Motion") + "-" + $scope.motion.identifier + ".pdf";
pdfMake.createPdf(documentProvider.getDocument()).download(filename);
});

View File

@ -6,21 +6,199 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
.factory('MotionContentProvider', [
'gettextCatalog',
'PdfPredefinedFunctions',
function(gettextCatalog, PdfPredefinedFunctions) {
'PDFLayout',
'Category',
'Config',
function(gettextCatalog, PDFLayout, Category, Config) {
/**
* Provides the content as JS objects for Motions in pdfMake context
* @constructor
*/
var createInstance = function(converter, motion, $scope, User) {
var createInstance = function(converter, motion, $scope) {
// title
var identifier = motion.identifier ? ' ' + motion.identifier : '';
var header = PdfPredefinedFunctions.createTitle(gettextCatalog.getString("Motion") + identifier +
': ' + motion.getTitle($scope.version));
var title = PDFLayout.createTitle(
gettextCatalog.getString('Motion') + identifier + ': ' +
motion.getTitle($scope.version)
);
// generates the text of the motion. Also septerates between line-numbers
var textContent = function() {
// subtitle
var subtitle = PDFLayout.createSubtitle(
gettextCatalog.getString('Sequential number') + ': ' + motion.id
);
// meta data table
var metaTable = function() {
var metaTableBody = [];
// submitters
var submitters = _.map(motion.submitters, function (submitter) {
return submitter.get_full_name();
}).join(', ');
metaTableBody.push([
{
text: gettextCatalog.getString('Submitters') + ':',
style: ['bold', 'grey']
},
{
text: submitters,
style: 'grey'
}
]);
// state
metaTableBody.push([
{
text: gettextCatalog.getString('State') + ':',
style: ['bold', 'grey']
},
{
text: motion.getStateName(),
style: 'grey'
}
]);
// recommendation
if (motion.getRecommendationName()) {
metaTableBody.push([
{
text: Config.get('motions_recommendations_by').value + ':',
style: ['bold', 'grey']
},
{
text: motion.getRecommendationName(),
style: 'grey'
}
]);
}
// category
if (motion.category) {
metaTableBody.push([
{
text: gettextCatalog.getString('Category') + ':',
style: ['bold', 'grey']
},
{
text: motion.category.name,
style: 'grey'
}
]);
}
// voting result
var column1 = [];
var column2 = [];
var column3 = [];
motion.polls.map(function(poll, index) {
var votenumber = '';
if (motion.polls.length > 1) {
votenumber = index + 1 + '. ' + gettextCatalog.getString('Vote');
}
// yes
var yes = poll.getVote(poll.yes, 'yes');
column1.push(gettextCatalog.getString('Yes') + ':');
column2.push(yes.value);
column3.push(yes.percentStr);
// no
var no = poll.getVote(poll.no, 'no');
column1.push(gettextCatalog.getString('No') + ':');
column2.push(no.value);
column3.push(no.percentStr);
// abstain
var abstain = poll.getVote(poll.abstain, 'abstain');
column1.push(gettextCatalog.getString('Abstain') + ':');
column2.push(abstain.value);
column3.push(abstain.percentStr);
// votes valid
if (poll.votesvalid) {
var valid = poll.getVote(poll.votesvalid, 'votesvalid');
column1.push(gettextCatalog.getString('Valid votes') + ':');
column2.push(valid.value);
column3.push(valid.percentStr);
}
// votes invalid
if (poll.votesvalid) {
var invalid = poll.getVote(poll.votesinvalid, 'votesinvalid');
column1.push(gettextCatalog.getString('Invalid votes') + ':');
column2.push(invalid.value);
column3.push(invalid.percentStr);
}
// votes cast
if (poll.votescast) {
var cast = poll.getVote(poll.votescast, 'votescast');
column1.push(gettextCatalog.getString('Votes cast') + ':');
column2.push(cast.value);
column3.push(cast.percentStr);
}
});
metaTableBody.push([
{
text: gettextCatalog.getString('Voting result') + ':',
style: ['bold', 'grey']
},
{
columns: [
{
text: column1.join('\n'),
width: 'auto'
},
{
text: column2.join('\n'),
width: 'auto',
alignment: 'right'
},
{
text: column3.join('\n'),
width: 'auto',
alignment: 'right'
},
],
columnGap: 7,
style: 'grey'
}
]);
// build table
var metaTableJsonString = {
table: {
widths: ['30%','70%'],
body: metaTableBody,
},
margin: [0, 0, 0, 20],
layout: {
hLineWidth: function(i, node) {
return (i === 0 || i === node.table.body.length) ? 0 : 0.5;
},
vLineWidth: function(i, node) {
return (i === 0 || i === node.table.widths.length) ? 0 : 0;
},
hLineColor: function(i, node) {
return (i === 0 || i === node.table.body.length) ? '' : 'white';
},
vLineColor: function(i, node) {
return (i === 0 || i === node.table.widths.length) ? '' : 'white';
}
}
};
return metaTableJsonString;
};
// motion title
var motionTitle = function() {
return [{
text: motion.getTitle($scope.version),
style: 'heading3'
}];
};
// motion text (with line-numbers)
var motionText = function() {
if ($scope.lineNumberMode == "inline" || $scope.lineNumberMode == "outside") {
/* in order to distinguish between the line-number-types we need to pass the scope
* to the convertHTML function.
@ -35,107 +213,16 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
}
};
// Generate text of reason
var reasonContent = function() {
return converter.convertHTML(motion.getReason($scope.version), $scope);
// motion reason heading
var motionReason = function() {
var reason = [{
text: gettextCatalog.getString('Reason'),
style: 'heading3'
}];
reason.push(converter.convertHTML(motion.getReason($scope.version), $scope));
return reason;
};
// Generate text of signment
var signment = function() {
var label = converter.createElement("text", gettextCatalog.getString('Submitter') + ':\nStatus:');
var state = converter.createElement("text", User.get(motion.submitters_id[0]).full_name + '\n' + motion.getStateName());
state.width = "70%";
label.width = "30%";
label.bold = true;
var signment = converter.createElement("columns", [label, state]);
signment.margin = [10, 20, 0, 10];
signment.lineHeight = 2.5;
return signment;
};
// Generates polls
var polls = function() {
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 ? poll.yes : '-', // if no poll.yes is given set it to '-'
yesRelative = poll.getVote(poll.yes, 'yes').percentStr,
no = poll.no ? poll.no : '-',
noRelative = poll.getVote(poll.no, 'no').percentStr,
abstain = poll.abstain ? poll.abstain : '-',
abstainrelativeGet = poll.getVote(poll.abstain, 'abstain').percentStr,
abstainRelative = abstainrelativeGet ? abstainrelativeGet : '',
valid = poll.votesvalid ? poll.votesvalid : '-',
validRelative = poll.getVote(poll.votesvalid, 'votesvalid').percentStr,
number = {
text: id + ".",
width: "5%"
},
headerText = {
text: gettextCatalog.getString('Vote'),
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
var titleSection = function() {
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
var reason = function() {
var r = converter.createElement("text", gettextCatalog.getString("Reason") + ":");
r.bold = true;
r.fontSize = 14;
r.margin = [0, 30, 0, 10];
return r;
};
// getters
var getTitle = function() {
@ -152,25 +239,17 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
// Generates content as a pdfmake consumable
var getContent = function() {
if (reasonContent().length === 0 ) {
return [
header,
signment(),
polls(),
titleSection(),
textContent(),
];
} else {
return [
header,
signment(),
polls(),
titleSection(),
textContent(),
reason(),
reasonContent()
var content = [
title,
subtitle,
metaTable(),
motionTitle(),
motionText(),
];
if (motionReason()) {
content.push(motionReason());
}
return content;
};
return {
getContent: getContent,
@ -186,8 +265,8 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
}])
.factory('PollContentProvider', [
'PdfPredefinedFunctions',
function(PdfPredefinedFunctions) {
'PDFLayout',
function(PDFLayout) {
/**
* Generates a content provider for polls
* @constructor
@ -211,9 +290,9 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
text: title,
style: 'description'
},
PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("Yes")),
PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("No")),
PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("Abstain")),
PDFLayout.createBallotEntry(gettextCatalog.getString("Yes")),
PDFLayout.createBallotEntry(gettextCatalog.getString("No")),
PDFLayout.createBallotEntry(gettextCatalog.getString("Abstain")),
],
margin: [0, 0, 0, sheetend]
};
@ -237,7 +316,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
[createSection(), createSection()]
],
},
layout: PdfPredefinedFunctions.getBallotLayoutLines()
layout: PDFLayout.getBallotLayoutLines()
}];
};
@ -252,20 +331,20 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
.factory('MotionCatalogContentProvider', [
'gettextCatalog',
'PdfPredefinedFunctions',
'PDFLayout',
'Category',
'Config',
function(gettextCatalog, PdfPredefinedFunctions, Config) {
function(gettextCatalog, PDFLayout, Category, Config) {
/**
* Constructor
* @function
* @param {object} allMotions - A sorted array of all motions to parse
* @param {object} $scope - Current $scope
* @param {object} User - Current user
*/
var createInstance = function(allMotions, $scope, User, Category) {
var createInstance = function(allMotions, $scope) {
var title = PdfPredefinedFunctions.createTitle(
var title = PDFLayout.createTitle(
gettextCatalog.getString(Config.get('motions_export_title').value)
);
@ -284,7 +363,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
var createTOContent = function() {
var heading = {
text: gettextCatalog.getString("Table of contents"),
style: "heading"
style: "heading2"
};
var toc = [];
@ -310,7 +389,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
return [
heading,
toc,
PdfPredefinedFunctions.addPageBreak()
PDFLayout.addPageBreak()
];
};
@ -319,7 +398,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
if (Category.getAll().length > 0) {
var heading = {
text: gettextCatalog.getString("Categories"),
style: "heading"
style: "heading2"
};
var toc = [];
@ -344,7 +423,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
return [
heading,
toc,
PdfPredefinedFunctions.addPageBreak()
PDFLayout.addPageBreak()
];
} else {
// if there are no categories, return "empty string"
@ -359,7 +438,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
angular.forEach(allMotions, function(motion, key) {
motionContent.push(motion.getContent());
if (key < allMotions.length - 1) {
motionContent.push(PdfPredefinedFunctions.addPageBreak());
motionContent.push(PDFLayout.addPageBreak());
}
});

View File

@ -926,7 +926,7 @@ angular.module('OpenSlidesApp.motions.site', [
//post-request to convert the images. Async.
$http.post('/core/encode_media/', JSON.stringify(image_sources)).success(function(data) {
var converter = PdfMakeConverter.createInstance(data.images, data.fonts, pdfMake);
var converter = PdfMakeConverter.createInstance(data.images, pdfMake);
var motionContentProviderArray = [];
//convert the filtered motions to motionContentProviders
@ -934,7 +934,7 @@ angular.module('OpenSlidesApp.motions.site', [
motionContentProviderArray.push(MotionContentProvider.createInstance(converter, motion, $scope, User, $http));
});
var motionCatalogContentProvider = MotionCatalogContentProvider.createInstance(motionContentProviderArray, $scope, User, Category);
var documentProvider = PdfMakeDocumentProvider.createInstance(motionCatalogContentProvider, data.defaultFont);
var documentProvider = PdfMakeDocumentProvider.createInstance(motionCatalogContentProvider);
pdfMake.createPdf(documentProvider.getDocument()).download(filename);
});
};

View File

@ -132,11 +132,13 @@
<!-- Recommendation -->
<div ng-if="config('motions_recommendations_by') != ''">
<h3 ng-if="!motion.isAllowed('change_recommendation')" class="heading" translate>Recommendation</h3>
<h3 ng-if="!motion.isAllowed('change_recommendation')" class="heading">
{{ config('motions_recommendations_by') }}
</h3>
<div ng-if="motion.isAllowed('change_recommendation')" class="heading">
<span uib-dropdown>
<a href id="recommendation-dropdown" class="drop-down-name" uib-dropdown-toggle>
<translate>Recommendation</translate>
{{ config('motions_recommendations_by') }}
<i class="fa fa-cog"></i>
</a>
<ul uib-dropdown-menu class="dropdown-menu" aria-labelledby="recommendation-dropdown">

View File

@ -6,13 +6,13 @@ angular.module('OpenSlidesApp.users.pdf', ['OpenSlidesApp.core.pdf'])
.factory('UserListContentProvider', [
'gettextCatalog',
'PdfPredefinedFunctions',
function(gettextCatalog, PdfPredefinedFunctions) {
'PDFLayout',
function(gettextCatalog, PDFLayout) {
var createInstance = function(userList, groups) {
//use the Predefined Functions to create the title
var title = PdfPredefinedFunctions.createTitle(gettextCatalog.getString("List of participants"));
var title = PDFLayout.createTitle(gettextCatalog.getString("List of participants"));
//function to generate the user list
var createUserList = function() {
@ -35,19 +35,19 @@ angular.module('OpenSlidesApp.users.pdf', ['OpenSlidesApp.core.pdf'])
var userJsonObj = [
{
text: "" + (counter+1),
style: PdfPredefinedFunctions.flipTableRowStyle(userJsonList.length)
style: PDFLayout.flipTableRowStyle(userJsonList.length)
},
{
text: user.short_name,
style: PdfPredefinedFunctions.flipTableRowStyle(userJsonList.length)
style: PDFLayout.flipTableRowStyle(userJsonList.length)
},
{
text: user.structure_level,
style: PdfPredefinedFunctions.flipTableRowStyle(userJsonList.length)
style: PDFLayout.flipTableRowStyle(userJsonList.length)
},
{
text: userGroups.join(" "),
style: PdfPredefinedFunctions.flipTableRowStyle(userJsonList.length)
style: PDFLayout.flipTableRowStyle(userJsonList.length)
}
];
userJsonList.push(userJsonObj);
@ -107,8 +107,8 @@ angular.module('OpenSlidesApp.users.pdf', ['OpenSlidesApp.core.pdf'])
.factory('UserAccessDataListContentProvider', [
'gettextCatalog',
'PdfPredefinedFunctions',
function(gettextCatalog, PdfPredefinedFunctions) {
'PDFLayout',
function(gettextCatalog, PDFLayout) {
var createInstance = function(userList, groups, Config) {