Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/strange-pots-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@react-pdf/textkit': major
'@react-pdf/layout': patch
'@react-pdf/render': patch
---

feat: build textkit with rollup & define public api
32 changes: 17 additions & 15 deletions packages/layout/src/svg/layoutText.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as P from '@react-pdf/primitives';
import layoutEngine from '@react-pdf/textkit/lib/layout';
import linebreaker from '@react-pdf/textkit/lib/engines/linebreaker';
import justification from '@react-pdf/textkit/lib/engines/justification';
import scriptItemizer from '@react-pdf/textkit/lib/engines/scriptItemizer';
import wordHyphenation from '@react-pdf/textkit/lib/engines/wordHyphenation';
import decorationEngine from '@react-pdf/textkit/lib/engines/textDecoration';
import fromFragments from '@react-pdf/textkit/lib/attributedString/fromFragments';
import layoutEngine, {
linebreaker,
justification,
scriptItemizer,
wordHyphenation,
textDecoration,
} from '@react-pdf/textkit';

import fromFragments from '../text/fromFragments';
import transformText from '../text/transformText';
import fontSubstitution from '../text/fontSubstitution';

Expand All @@ -15,10 +16,10 @@ const isTextInstance = node => node.type === P.TextInstance;
const engines = {
linebreaker,
justification,
textDecoration,
scriptItemizer,
wordHyphenation,
fontSubstitution,
textDecoration: decorationEngine,
};

const engine = layoutEngine(engines);
Expand All @@ -34,13 +35,14 @@ const getFragments = (fontStore, instance) => {
fontWeight,
fontStyle,
fontSize = 18,
textDecoration,
textDecorationColor,
textDecorationStyle,
textTransform,
opacity,
} = instance.props;

const _textDecoration = instance.props.textDecoration;

const obj = fontStore
? fontStore.getFont({ fontFamily, fontWeight, fontStyle })
: null;
Expand All @@ -53,14 +55,14 @@ const getFragments = (fontStore, instance) => {
color: fill,
underlineStyle: textDecorationStyle,
underline:
textDecoration === 'underline' ||
textDecoration === 'underline line-through' ||
textDecoration === 'line-through underline',
_textDecoration === 'underline' ||
_textDecoration === 'underline line-through' ||
_textDecoration === 'line-through underline',
underlineColor: textDecorationColor || fill,
strike:
textDecoration === 'line-through' ||
textDecoration === 'underline line-through' ||
textDecoration === 'line-through underline',
_textDecoration === 'line-through' ||
_textDecoration === 'underline line-through' ||
_textDecoration === 'line-through underline',
strikeStyle: textDecorationStyle,
strikeColor: textDecorationColor || fill,
};
Expand Down
27 changes: 27 additions & 0 deletions packages/layout/src/text/fromFragments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Create attributed string from text fragments
*
* @param {Array} fragments
* @return {Object} attributed string
*/
const fromFragments = fragments => {
let offset = 0;
let string = '';
const runs = [];

fragments.forEach(fragment => {
string += fragment.string;

runs.push({
start: offset,
end: offset + fragment.string.length,
attributes: fragment.attributes || {},
});

offset += fragment.string.length;
});

return { string, runs };
};

export default fromFragments;
2 changes: 1 addition & 1 deletion packages/layout/src/text/getAttributedString.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as P from '@react-pdf/primitives';
import fromFragments from '@react-pdf/textkit/lib/attributedString/fromFragments';

import { embedEmojis } from './emoji';
import ignoreChars from './ignoreChars';
import fromFragments from './fromFragments';
import transformText from './transformText';

const PREPROCESSORS = [ignoreChars, embedEmojis];
Expand Down
13 changes: 7 additions & 6 deletions packages/layout/src/text/layoutText.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import layoutEngine from '@react-pdf/textkit/lib/layout';
import linebreaker from '@react-pdf/textkit/lib/engines/linebreaker';
import justification from '@react-pdf/textkit/lib/engines/justification';
import textDecoration from '@react-pdf/textkit/lib/engines/textDecoration';
import scriptItemizer from '@react-pdf/textkit/lib/engines/scriptItemizer';
import wordHyphenation from '@react-pdf/textkit/lib/engines/wordHyphenation';
import layoutEngine, {
linebreaker,
justification,
scriptItemizer,
wordHyphenation,
textDecoration,
} from '@react-pdf/textkit';

import fontSubstitution from './fontSubstitution';
import getAttributedString from './getAttributedString';
Expand Down
5 changes: 2 additions & 3 deletions packages/layout/src/text/linesWidth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import advanceWidth from '@react-pdf/textkit/lib/attributedString/advanceWidth';

/**
* Get lines width (if any)
*
Expand All @@ -8,7 +6,8 @@ import advanceWidth from '@react-pdf/textkit/lib/attributedString/advanceWidth';
*/
const linesWidth = node => {
if (!node.lines) return 0;
return Math.max(0, ...node.lines.map(line => advanceWidth(line)));

return Math.max(0, ...node.lines.map(line => line.xAdvance));
};

export default linesWidth;
41 changes: 41 additions & 0 deletions packages/layout/tests/text/fromFragments.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import fromFragments from '../../src/text/fromFragments';

describe('attributeString fromFragments operator', () => {
test('should return empty attributed string for no fragments', () => {
const attributedString = fromFragments([]);

expect(attributedString.string).toBe('');
expect(attributedString.runs).toHaveLength(0);
});

test('should be constructed by one fragment', () => {
const attributedString = fromFragments([{ string: 'Hey' }]);

expect(attributedString.string).toBe('Hey');
expect(attributedString.runs[0]).toHaveProperty('start', 0);
expect(attributedString.runs[0]).toHaveProperty('end', 3);
});

test('should be constructed by fragments', () => {
const attributedString = fromFragments([
{ string: 'Hey' },
{ string: ' ho' },
]);

expect(attributedString.string).toBe('Hey ho');
expect(attributedString.runs[0]).toHaveProperty('start', 0);
expect(attributedString.runs[0]).toHaveProperty('end', 3);
expect(attributedString.runs[1]).toHaveProperty('start', 3);
expect(attributedString.runs[1]).toHaveProperty('end', 6);
});

test('should preserve fragment attributes', () => {
const attributedString = fromFragments([
{ string: 'Hey', attributes: { attr: 1 } },
{ string: ' ho', attributes: { attr: 2 } },
]);

expect(attributedString.runs[0]).toHaveProperty('attributes', { attr: 1 });
expect(attributedString.runs[1]).toHaveProperty('attributes', { attr: 2 });
});
});
5 changes: 2 additions & 3 deletions packages/layout/tests/text/layoutText.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as P from '@react-pdf/primitives';
import runWidth from '@react-pdf/textkit/lib/run/advanceWidth';

import layoutText from '../../src/text/layoutText';

Expand Down Expand Up @@ -38,15 +37,15 @@ describe('text layoutText', () => {
test('Should render aligned right text', async () => {
const node = createTextNode(TEXT, { textAlign: 'right' });
const lines = layoutText(node, 1500, 30, null);
const textWidth = runWidth(lines[0].runs[0]);
const textWidth = lines[0].runs[0].xAdvance;

expect(lines[0].box.x).toBe(1500 - textWidth);
});

test('Should render aligned center text', async () => {
const node = createTextNode(TEXT, { textAlign: 'center' });
const lines = layoutText(node, 1500, 30, null);
const textWidth = runWidth(lines[0].runs[0]);
const textWidth = lines[0].runs[0].xAdvance;

expect(lines[0].box.x).toBe((1500 - textWidth) / 2);
});
Expand Down
7 changes: 2 additions & 5 deletions packages/render/src/primitives/renderSvgText.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import runWidth from '@react-pdf/textkit/lib/run/advanceWidth';
import lineWidth from '@react-pdf/textkit/lib/attributedString/advanceWidth';

import renderGlyphs from './renderGlyphs';

const renderRun = (ctx, run) => {
const runAdvanceWidth = runWidth(run);
const runAdvanceWidth = run.xAdvance;
const { font, fontSize, color, opacity } = run.attributes;

ctx.fillColor(color);
Expand Down Expand Up @@ -48,7 +45,7 @@ const renderSpan = (ctx, line, textAnchor, dominantBaseline) => {
const y = line.box?.y || 0;
const font = line.runs[0]?.attributes.font;
const scale = line.runs[0]?.attributes?.scale || 1;
const width = lineWidth(line);
const width = line.xAdvance;

const ascent = font.ascent * scale;
const xHeight = font.xHeight * scale;
Expand Down
20 changes: 7 additions & 13 deletions packages/render/src/primitives/renderText.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
/* eslint-disable no-param-reassign */
import { isNil } from '@react-pdf/fns';
import runHeight from '@react-pdf/textkit/lib/run/height';
import runDescent from '@react-pdf/textkit/lib/run/descent';
import advanceWidth from '@react-pdf/textkit/lib/run/advanceWidth';
import ascent from '@react-pdf/textkit/lib/attributedString/ascent';

import renderGlyphs from './renderGlyphs';
import parseColor from '../utils/parseColor';
Expand Down Expand Up @@ -56,22 +52,20 @@ const renderRun = (ctx, run, options) => {
? color.opacity
: run.attributes.opacity;

const height = runHeight(run);
const descent = runDescent(run);
const runAdvanceWidth = advanceWidth(run);
const { height, descent, xAdvance } = run;

if (options.outlineRuns) {
ctx.rect(0, -height, runAdvanceWidth, height).stroke();
ctx.rect(0, -height, xAdvance, height).stroke();
}

ctx.fillColor(color.value);
ctx.fillOpacity(opacity);

if (link) {
if (isSrcId(link)) {
ctx.goTo(0, -height - descent, runAdvanceWidth, height, link.slice(1));
ctx.goTo(0, -height - descent, xAdvance, height, link.slice(1));
} else {
ctx.link(0, -height - descent, runAdvanceWidth, height, link);
ctx.link(0, -height - descent, xAdvance, height, link);
}
}

Expand Down Expand Up @@ -105,7 +99,7 @@ const renderRun = (ctx, run, options) => {
}
}

ctx.translate(runAdvanceWidth, 0);
ctx.translate(xAdvance, 0);
};

const renderBackground = (ctx, rect, backgroundColor) => {
Expand Down Expand Up @@ -174,7 +168,7 @@ const renderDecorationLine = (ctx, line) => {
};

const renderLine = (ctx, line, options) => {
const lineAscent = ascent(line);
const lineAscent = line.ascent;

if (options.outlineLines) {
ctx.rect(line.box.x, line.box.y, line.box.width, line.box.height).stroke();
Expand All @@ -194,7 +188,7 @@ const renderLine = (ctx, line, options) => {
x: 0,
y: -lineAscent,
height: line.box.height,
width: advanceWidth(run) - overflowRight,
width: run.xAdvance - overflowRight,
};

renderBackground(ctx, backgroundRect, run.attributes.backgroundColor);
Expand Down
4 changes: 3 additions & 1 deletion packages/textkit/babel.config.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module.exports = { extends: '../../babel.config.js' };
module.exports = {
extends: '../../babel.config.js',
};
7 changes: 4 additions & 3 deletions packages/textkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"license": "MIT",
"version": "3.0.0",
"description": "An advanced text layout framework",
"main": "./lib/index.js",
"main": "./lib/textkit.cjs.js",
"module": "./lib/textkit.es.js",
"repository": {
"type": "git",
"url": "https://github.com/diegomura/react-pdf.git",
Expand All @@ -15,8 +16,8 @@
],
"scripts": {
"test": "jest",
"build": "rimraf ./lib && babel src --out-dir lib",
"watch": "rimraf ./lib && babel src --out-dir lib --watch"
"build": "rimraf ./lib && rollup -c",
"watch": "rimraf ./lib && rollup -c -w"
},
"files": [
"lib"
Expand Down
37 changes: 37 additions & 0 deletions packages/textkit/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import babel from '@rollup/plugin-babel';
import localResolve from 'rollup-plugin-local-resolve';
import pkg from './package.json';

const cjs = {
exports: 'named',
format: 'cjs',
};

const esm = {
format: 'es',
};

const getCJS = override => Object.assign({}, cjs, override);
const getESM = override => Object.assign({}, esm, override);

const configBase = {
input: './src/index.js',
external: Object.keys(pkg.dependencies),
plugins: [
localResolve(),
babel({
babelrc: true,
babelHelpers: 'runtime',
exclude: 'node_modules/**',
}),
],
};

const config = Object.assign({}, configBase, {
output: [
getESM({ file: 'lib/textkit.es.js' }),
getCJS({ file: 'lib/textkit.cjs.js' }),
],
});

export default config;
6 changes: 2 additions & 4 deletions packages/textkit/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import scriptItemizer from './engines/scriptItemizer';
import wordHyphenation from './engines/wordHyphenation';
import fontSubstitution from './engines/fontSubstitution';

const engines = {
export {
linebreaker,
justification,
textDecoration,
Expand All @@ -15,6 +15,4 @@ const engines = {
fontSubstitution,
};

const engine = layoutEngine(engines);

export default engine;
export default layoutEngine;
Loading