Skip to content

Commit

Permalink
Add Font support (#117)
Browse files Browse the repository at this point in the history
Fixes #44
  • Loading branch information
diegomura authored Jul 7, 2017
1 parent c379e1e commit 61d41d0
Show file tree
Hide file tree
Showing 18 changed files with 243 additions and 47 deletions.
Binary file modified examples/fractals/output.pdf
Binary file not shown.
Binary file modified examples/page-layout/output.pdf
Binary file not shown.
Binary file added examples/text/fonts/Roboto-Regular.ttf
Binary file not shown.
18 changes: 17 additions & 1 deletion examples/text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@

import React from 'react';
import ReactPDF from '@react-pdf/node';
import { Document, Page, View, Text, Link, StyleSheet } from '@react-pdf/core';
import {
Document,
Page,
View,
Text,
Link,
Font,
StyleSheet,
} from '@react-pdf/core';

const styles = StyleSheet.create({
title: {
Expand All @@ -13,6 +21,7 @@ const styles = StyleSheet.create({
backgroundColor: '#e4e4e4',
textDecoration: 'underline',
textTransform: 'uppercase',
fontFamily: 'Roboto',
},
body: {
flexGrow: 1,
Expand All @@ -27,6 +36,7 @@ const styles = StyleSheet.create({
text: {
flexGrow: 3,
margin: 10,
fontFamily: 'Oswald',
},
fill1: {
flexGrow: 2,
Expand All @@ -46,6 +56,12 @@ const styles = StyleSheet.create({
},
});

Font.register(`${__dirname}/fonts/Roboto-Regular.ttf`, { family: 'Roboto' });
Font.register(
'https://fonts.gstatic.com/s/oswald/v13/Y_TKV6o8WovbUd3m_X9aAA.ttf',
{ family: 'Oswald' },
);

const doc = (
<Document>
<Page size="A4">
Expand Down
Binary file modified examples/text/output.pdf
Binary file not shown.
Binary file modified examples/text/thumb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/react-pdf/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"dependencies": {
"fbjs": "^0.8.4",
"fontkit": "^1.5.4",
"is-url": "^1.2.2",
"lodash.isfunction": "^3.0.8",
"lodash.isnan": "^3.0.2",
"lodash.map": "^4.6.0",
Expand Down
Binary file not shown.
67 changes: 67 additions & 0 deletions packages/react-pdf/specs/fontSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { Document, Page, Text, Font } from '../src';
import MockDate from 'mockdate';
import render from './testRenderer';

const oswaldUrl =
'https://fonts.gstatic.com/s/oswald/v13/Y_TKV6o8WovbUd3m_X9aAA.ttf';

const lorem =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. \
Proin bibendum, diam non dictum rutrum, ligula velit molestie leo, sit \
amet suscipit purus ipsum et ligula. Cras placerat, tellus fringilla viverra \
maximus, ex metus vulputate ante, finibus dapibus eros dolor fermentum massa.';

describe('Font', () => {
beforeEach(() => {
MockDate.set(1434319925275);

// pdfkit generates a random tag for internal font name,
// so we mock Math.random to *not* be random
const mockMath = Object.create(global.Math);
mockMath.random = () => 0.5;
global.Math = mockMath;
});

afterEach(() => {
Font.clear();
});

const matchSnapshot = (doc, done) =>
render(<Document>{doc}</Document>).then(result => {
expect(result).toMatchSnapshot();
done();
});

test('should be able to register font families', () => {
Font.register('src', { family: 'MyFont' });
Font.register('src', { family: 'MyOtherFont' });

expect(Font.getRegisteredFonts()).toEqual(['MyFont', 'MyOtherFont']);
});

test('should be able to clear registered fonts', () => {
Font.register('src', { family: 'MyFont' });

expect(Font.getRegisteredFonts()).toEqual(['MyFont']);

Font.clear();

expect(Font.getRegisteredFonts()).toEqual([]);
});

test('should be able to load font from url', done => {
Font.register(oswaldUrl, { family: 'Oswald' });

matchSnapshot(
<Page>
<Text style={{ fontFamily: 'Oswald' }}>
{lorem}
</Text>
</Page>,
done,
);
});

test('should be able to load a font from file');
});
11 changes: 3 additions & 8 deletions packages/react-pdf/src/elements/Base.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,9 @@ class Base {
}

async renderChildren() {
const childRenders = await Promise.all(
this.children.map(child => child.render()),
);
return childRenders;
}

async render(value) {
return [value, await this.renderChildren()].join('');
for (let i = 0; i < this.children.length; i++) {
await this.children[i].render();
}
}
}

Expand Down
7 changes: 3 additions & 4 deletions packages/react-pdf/src/elements/Document.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ class Document {
}

async renderChildren() {
const childRenders = await Promise.all(
this.children.map(child => child.render()),
);
return childRenders;
for (let i = 0; i < this.children.length; i++) {
await this.children[i].render();
}
}

async render() {
Expand Down
74 changes: 45 additions & 29 deletions packages/react-pdf/src/elements/Link.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,64 @@
import Text from './Text';

const PROTOCOL_REGEXP = /^(http|https|ftp|ftps|mailto)\:\/\//i;

class Link extends Text {
async render({ inline } = {}) {
getSrc() {
let { src } = this.props;

if (typeof src === 'string' && !src.match(PROTOCOL_REGEXP)) {
src = `http://${src}`;
}

return src;
}

renderInlineLink() {
const {
fontSize = 18,
color = 'blue',
textDecoration = 'underline',
} = this.style;

this.root.fillColor(color).fontSize(fontSize).text(this.children, {
link: this.getSrc(),
continued: true,
underline: textDecoration === 'underline',
});
}

renderBlockLink() {
const {
align,
fontSize = 18,
color = 'blue',
textDecoration = 'underline',
} = this.style;

if (inline) {
this.root.fillColor(color).fontSize(fontSize).text(this.children, {
link: this._getSrc(),
continued: true,
const { left, top, width, height } = this.getAbsoluteLayout();

this.drawBackgroundColor();

this.root
.fillColor(color)
.fontSize(fontSize)
.text(this.children, left, top, {
link: this.getSrc(),
width: width + 0.1,
height: height + 0.1,
align,
underline: textDecoration === 'underline',
});
} else {
this.drawBackgroundColor();

const { left, top, width, height } = this.getAbsoluteLayout();

this.root
.fillColor(color)
.fontSize(fontSize)
.text(this.children, left, top, {
link: this._getSrc(),
width: width + 0.1,
height: height + 0.1,
align,
underline: textDecoration === 'underline',
});
}
}

_getSrc() {
const PROTOCOL_REGEXP = /^(http|https|ftp|ftps|mailto)\:\/\//i;

let { src } = this.props;
async render({ inline } = {}) {
await this.loadFont();

if (typeof src === 'string' && !src.match(PROTOCOL_REGEXP)) {
src = `http://${src}`;
if (inline) {
this.renderInlineLink();
} else {
this.renderBlockLink();
}

return src;
}
}

Expand Down
27 changes: 22 additions & 5 deletions packages/react-pdf/src/elements/Text.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Base from './Base';
import Font from '../font';
import Yoga from 'yoga-layout';
import isNan from 'lodash.isnan';
import upperFirst from 'lodash.upperfirst';
Expand Down Expand Up @@ -106,9 +107,22 @@ class Text extends Base {
this.layout.markDirty();
}

renderText(text, isFirstNode) {
async loadFont() {
const { fontFamily } = this.style;

if (fontFamily) {
await Font.load(fontFamily, this.root);
this.root.font(fontFamily);
} else {
this.root.font('Helvetica');
}
}

async renderText(text, isFirstNode) {
const { align = 'left', textDecoration } = this.style;

await this.loadFont();

this.root.text(text, {
align,
link: '',
Expand All @@ -132,15 +146,18 @@ class Text extends Base {
height: height - padding.top - padding.bottom + 0.1,
});

this.children.forEach((child, index) => {
// Render childs: text and inline elements
for (let i = 0; i < this.children.length; i++) {
const child = this.children[i];

this.root.fillColor(color).fontSize(fontSize);

if (typeof child === 'string') {
this.renderText(child);
await this.renderText(child);
} else {
child.render({ inline: true });
await child.render({ inline: true });
}
});
}

// Text should not longer be continuos
this.root.text('', { continued: false });
Expand Down
44 changes: 44 additions & 0 deletions packages/react-pdf/src/font/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import isUrl from 'is-url';
import standardFonts from './standard';
import { fetchFont } from '../utils/font';

let fonts = {};

const register = (src, { family, ...otherOptions }) => {
fonts[family] = {
src,
loaded: false,
...otherOptions,
};
};

const getRegisteredFonts = () => Object.keys(fonts);

const load = async (fontFamily, doc) => {
const font = fonts[fontFamily];

if (font && !font.loaded) {
font.loaded = true;

const src = isUrl(font.src) ? await fetchFont(font.src) : font.src;

doc.registerFont(fontFamily, src);
}

if (!font && !standardFonts.includes(fontFamily)) {
throw new Error(
`Font familiy not registered: ${fontFamily}. Please register it calling Font.register() method.`,
);
}
};

const clear = () => {
fonts = {};
};

export default {
register,
getRegisteredFonts,
load,
clear,
};
16 changes: 16 additions & 0 deletions packages/react-pdf/src/font/standard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default [
'Courier',
'Courier-Bold',
'Courier-Oblique',
'Courier-BoldOblique',
'Helvetica',
'Helvetica-Bold',
'Helvetica-Oblique',
'Helvetica-BoldOblique',
'Times-Roman',
'Times-Bold',
'Times-Italic',
'Times-BoldItalic',
'Symbol',
'ZapfDingbats',
];
2 changes: 2 additions & 0 deletions packages/react-pdf/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { PDFRenderer, createElement } from './renderer';
import StyleSheet from './stylesheet';
import Font from './font';

const View = 'VIEW';
const Text = 'TEXT';
Expand Down Expand Up @@ -68,6 +69,7 @@ export {
Text,
Link,
Page,
Font,
Image,
Document,
StyleSheet,
Expand Down
19 changes: 19 additions & 0 deletions packages/react-pdf/src/utils/font.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const request = require('request');

export const fetchFont = src =>
new Promise((resolve, reject) => {
request(
{
url: src,
method: 'GET',
encoding: null,
},
(error, response, body) => {
if (error) {
return reject(error);
}

return resolve(body);
},
);
});
Loading

0 comments on commit 61d41d0

Please sign in to comment.