Skip to content

Commit

Permalink
Merge pull request bpampuch#221 from TW-QEN/qen-fixToManyCharacterIssue
Browse files Browse the repository at this point in the history
Embed fonts multiple times to allow more than 92 characters.
  • Loading branch information
jthoenes committed Mar 13, 2015
2 parents 8fcc3cf + d050075 commit d34d4c2
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 82 deletions.
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-dump-dir');
grunt.loadNpmTasks('grunt-contrib-concat');

grunt.registerTask('test', [ 'replace:exposeTestMethods', 'jshint', 'mochacov', 'replace:hideTestMethods' ]);
grunt.registerTask('test', [ 'replace:fixPdfKit', 'replace:exposeTestMethods', 'jshint', 'mochacov', 'replace:hideTestMethods' ]);

grunt.registerTask('fixVfsFonts', 'Adds semicolon to the end of vfs_fonts.js', function () {
var file = grunt.file.read('build/vfs_fonts.js');
Expand Down
61 changes: 61 additions & 0 deletions src/fontProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* jslint node: true */
'use strict';

var _ = require('lodash');
var FontWrapper = require('./fontWrapper');

function typeName(bold, italics){
var type = 'normal';
if (bold && italics) type = 'bolditalics';
else if (bold) type = 'bold';
else if (italics) type = 'italics';
return type;
}

function FontProvider(fontDescriptors, pdfDoc) {
this.fonts = {};
this.pdfDoc = pdfDoc;
this.fontWrappers = {};

for(var font in fontDescriptors) {
if (fontDescriptors.hasOwnProperty(font)) {
var fontDef = fontDescriptors[font];

this.fonts[font] = {
normal: fontDef.normal,
bold: fontDef.bold,
italics: fontDef.italics,
bolditalics: fontDef.bolditalics
};
}
}
}

FontProvider.prototype.provideFont = function(familyName, bold, italics) {
if (!this.fonts[familyName]) return this.pdfDoc._font;
var type = typeName(bold, italics);

this.fontWrappers[familyName] = this.fontWrappers[familyName] || {};

if (!this.fontWrappers[familyName][type]) {
this.fontWrappers[familyName][type] = new FontWrapper(this.pdfDoc, this.fonts[familyName][type], familyName + '(' + type + ')');
}

return this.fontWrappers[familyName][type];
};

FontProvider.prototype.setFontRefsToPdfDoc = function(){
var self = this;

_.each(self.fontWrappers, function(fontFamily) {
_.each(fontFamily, function(fontWrapper){
_.each(fontWrapper.pdfFonts, function(font){
if (!self.pdfDoc.page.fonts[font.id]) {
self.pdfDoc.page.fonts[font.id] = font.ref();
}
});
});
});
};

module.exports = FontProvider;
106 changes: 106 additions & 0 deletions src/fontWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* jslint node: true */
'use strict';

var _ = require('lodash');

function FontWrapper(pdfkitDoc, path, fontName){
this.MAX_CHAR_TYPES = 92;

this.pdfkitDoc = pdfkitDoc;
this.path = path;
this.pdfFonts = [];
this.charCatalogue = [];
this.name = fontName;

this.__defineGetter__('ascender', function(){
var font = this.getFont(0);
return font.ascender;
});
this.__defineGetter__('decender', function(){
var font = this.getFont(0);
return font.decender;
});

}
// private

FontWrapper.prototype.getFont = function(index){
if(!this.pdfFonts[index]){

var pseudoName = this.name + index;

if(this.postscriptName){
delete this.pdfkitDoc._fontFamilies[this.postscriptName];
}

this.pdfFonts[index] = this.pdfkitDoc.font(this.path, pseudoName)._font;
if(!this.postscriptName){
this.postscriptName = this.pdfFonts[index].name;
}
}

return this.pdfFonts[index];
};

// public
FontWrapper.prototype.widthOfString = function(){
var font = this.getFont(0);
return font.widthOfString.apply(font, arguments);
};

FontWrapper.prototype.lineHeight = function(){
var font = this.getFont(0);
return font.lineHeight.apply(font, arguments);
};

FontWrapper.prototype.ref = function(){
var font = this.getFont(0);
return font.ref.apply(font, arguments);
};

var toCharCode = function(char){
return char.charCodeAt(0);
};

FontWrapper.prototype.encode = function(text){
var self = this;

var charTypesInInline = _.chain(text.split('')).map(toCharCode).uniq().value();
if (charTypesInInline.length > self.MAX_CHAR_TYPES) {
throw new Error('Inline has more than '+ self.MAX_CHAR_TYPES + ': ' + text + ' different character types and therefore cannot be properly embedded into pdf.');
}


var characterFitInFontWithIndex = function (charCatalogue) {

return _.uniq(charCatalogue.concat(charTypesInInline)).length <= self.MAX_CHAR_TYPES;
};

var index = _.findIndex(self.charCatalogue, characterFitInFontWithIndex);

if(index < 0){
index = self.charCatalogue.length;
self.charCatalogue[index] = [];
}

var font = this.getFont(index);
font.use(text);

_.each(charTypesInInline, function(charCode){
if(!_.includes(self.charCatalogue[index], charCode)){
self.charCatalogue[index].push(charCode);
}
});

var encodedText = _.map(font.encode(text), function (char) {
return char.charCodeAt(0).toString(16);
}).join('');

return {
encodedText: encodedText,
fontId: font.id
};
};


module.exports = FontWrapper;
93 changes: 13 additions & 80 deletions src/printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
/* global window */
'use strict';

var _ = require('lodash');
var FontProvider = require('./fontProvider');
var LayoutBuilder = require('./layoutBuilder');
var PdfKit = require('pdfkit');
var PDFReference = require('pdfkit/js/reference');
Expand Down Expand Up @@ -219,8 +221,6 @@ function renderPages(pages, fontProvider, pdfKitDoc) {
pdfKitDoc.addPage(pdfKitDoc.options);
}

setFontRefs(fontProvider, pdfKitDoc);

var page = pages[i];
for(var ii = 0, il = page.items.length; ii < il; ii++) {
var item = page.items[ii];
Expand All @@ -237,32 +237,18 @@ function renderPages(pages, fontProvider, pdfKitDoc) {
}
}
if(page.watermark){
renderWatermark(page, pdfKitDoc, fontProvider);
}
renderWatermark(page, pdfKitDoc);
}
}

function setFontRefs(fontProvider, pdfKitDoc) {
for(var fontName in fontProvider.cache) {
var desc = fontProvider.cache[fontName];

for (var fontType in desc) {
var font = desc[fontType];
var _ref, _base, _name;

if (!(_ref = (_base = pdfKitDoc.page.fonts)[_name = font.id])) {
_base[_name] = font.ref();
}
}
}
fontProvider.setFontRefsToPdfDoc();
}
}

function renderLine(line, x, y, pdfKitDoc) {
x = x || 0;
y = y || 0;

var ascenderHeight = line.getAscenderHeight();
var lineHeight = line.getHeight();

textDecorator.drawBackground(line, x, y, pdfKitDoc);

Expand All @@ -275,13 +261,14 @@ function renderLine(line, x, y, pdfKitDoc) {
pdfKitDoc.save();
pdfKitDoc.transform(1, 0, 0, -1, 0, pdfKitDoc.page.height);


var encoded = inline.font.encode(inline.text);
pdfKitDoc.addContent('BT');
var a = (inline.font.ascender / 1000 * inline.fontSize);

pdfKitDoc.addContent('' + (x + inline.x) + ' ' + (pdfKitDoc.page.height - y - ascenderHeight) + ' Td');
pdfKitDoc.addContent('/' + inline.font.id + ' ' + inline.fontSize + ' Tf');
pdfKitDoc.addContent('/' + encoded.fontId + ' ' + inline.fontSize + ' Tf');

pdfKitDoc.addContent('<' + encode(inline.font, inline.text) + '> Tj');
pdfKitDoc.addContent('<' + encoded.encodedText + '> Tj');

pdfKitDoc.addContent('ET');
pdfKitDoc.restore();
Expand All @@ -291,7 +278,7 @@ function renderLine(line, x, y, pdfKitDoc) {

}

function renderWatermark(page, pdfKitDoc, fontProvider){
function renderWatermark(page, pdfKitDoc){
var watermark = page.watermark;

pdfKitDoc.fill('black');
Expand All @@ -303,30 +290,15 @@ function renderWatermark(page, pdfKitDoc, fontProvider){
var angle = Math.atan2(pdfKitDoc.page.height, pdfKitDoc.page.width) * 180/Math.PI;
pdfKitDoc.rotate(angle, {origin: [pdfKitDoc.page.width/2, pdfKitDoc.page.height/2]});

var encoded = watermark.font.encode(watermark.text);
pdfKitDoc.addContent('BT');
pdfKitDoc.addContent('' + (pdfKitDoc.page.width/2 - watermark.size.size.width/2) + ' ' + (pdfKitDoc.page.height/2 - watermark.size.size.height/4) + ' Td');
pdfKitDoc.addContent('/' + watermark.font.id + ' ' + watermark.size.fontSize + ' Tf');
pdfKitDoc.addContent('<' + encode(watermark.font, watermark.text) + '> Tj');
pdfKitDoc.addContent('/' + encoded.fontId + ' ' + watermark.size.fontSize + ' Tf');
pdfKitDoc.addContent('<' + encoded.encodedText + '> Tj');
pdfKitDoc.addContent('ET');
pdfKitDoc.restore();
}

function encode(font, text) {
font.use(text);

text = font.encode(text);
text = ((function() {
var _results = [];

for (var i = 0, _ref2 = text.length; 0 <= _ref2 ? i < _ref2 : i > _ref2; 0 <= _ref2 ? i++ : i--) {
_results.push(text.charCodeAt(i).toString(16));
}
return _results;
})()).join('');

return text;
}

function renderVector(vector, pdfDoc) {
//TODO: pdf optimization (there's no need to write all properties everytime)
pdfDoc.lineWidth(vector.lineWidth || 1);
Expand Down Expand Up @@ -388,45 +360,6 @@ function renderImage(image, x, y, pdfKitDoc) {
pdfKitDoc.image(image.image, image.x, image.y, { width: image._width, height: image._height });
}

function FontProvider(fontDescriptors, pdfDoc) {
this.fonts = {};
this.pdfDoc = pdfDoc;
this.cache = {};

for(var font in fontDescriptors) {
if (fontDescriptors.hasOwnProperty(font)) {
var fontDef = fontDescriptors[font];

this.fonts[font] = {
normal: fontDef.normal,
bold: fontDef.bold,
italics: fontDef.italics,
bolditalics: fontDef.bolditalics
};
}
}
}

FontProvider.prototype.provideFont = function(familyName, bold, italics) {
if (!this.fonts[familyName]) return this.pdfDoc._font;

var type = 'normal';

if (bold && italics) type = 'bolditalics';
else if (bold) type = 'bold';
else if (italics) type = 'italics';

if (!this.cache[familyName]) this.cache[familyName] = {};

var cached = this.cache[familyName] && this.cache[familyName][type];

if (cached) return cached;

var fontCache = (this.cache[familyName] = this.cache[familyName] || {});
fontCache[type] = this.pdfDoc.font(this.fonts[familyName][type], familyName + ' (' + type + ')')._font;
return fontCache[type];
};

module.exports = PdfPrinter;


Expand Down
Loading

0 comments on commit d34d4c2

Please sign in to comment.