diff --git a/.changeset/afraid-schools-carry.md b/.changeset/afraid-schools-carry.md new file mode 100644 index 000000000..40f45cdff --- /dev/null +++ b/.changeset/afraid-schools-carry.md @@ -0,0 +1,7 @@ +--- +'@react-pdf/textkit': minor +'@react-pdf/layout': minor +'@react-pdf/types': minor +--- + +added base support for verticalAlign "super" and "sub" diff --git a/packages/layout/src/text/getAttributedString.js b/packages/layout/src/text/getAttributedString.js index cce85ba0f..1a9858949 100644 --- a/packages/layout/src/text/getAttributedString.js +++ b/packages/layout/src/text/getAttributedString.js @@ -38,6 +38,7 @@ const getFragments = (fontStore, instance, parentLink, level = 0) => { letterSpacing, textIndent, opacity, + verticalAlign, } = instance.style; const opts = { fontFamily, fontWeight, fontStyle }; @@ -70,6 +71,7 @@ const getFragments = (fontStore, instance, parentLink, level = 0) => { underlineColor: textDecorationColor || color, link: parentLink || instance.props?.src || instance.props?.href, lineHeight: lineHeight ? lineHeight * fontSize : null, + verticalAlign, }; for (let i = 0; i < instance.children.length; i += 1) { diff --git a/packages/renderer/tests/snapshots/text-test-js-text-should-support-vertical-align-super-and-sub-1-snap.png b/packages/renderer/tests/snapshots/text-test-js-text-should-support-vertical-align-super-and-sub-1-snap.png new file mode 100644 index 000000000..4beafbc1d Binary files /dev/null and b/packages/renderer/tests/snapshots/text-test-js-text-should-support-vertical-align-super-and-sub-1-snap.png differ diff --git a/packages/renderer/tests/text.test.js b/packages/renderer/tests/text.test.js index 7379c55de..a38cd07e7 100644 --- a/packages/renderer/tests/text.test.js +++ b/packages/renderer/tests/text.test.js @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-one-expression-per-line */ /* eslint-disable react/no-array-index-key */ import renderToImage from './renderComponent'; import { Document, Page, View, Text, Link, Font, StyleSheet } from '..'; @@ -101,4 +102,20 @@ describe('text', () => { expect(image).toMatchImageSnapshot(); }); + + test('should support verticalAlign super and sub', async () => { + const image = await renderToImage( + + + + Lorem + ipsum + dolor + + + , + ); + + expect(image).toMatchImageSnapshot(); + }); }); diff --git a/packages/textkit/src/layout/applyDefaultStyles.js b/packages/textkit/src/layout/applyDefaultStyles.js index d91bca8ce..b253e447d 100644 --- a/packages/textkit/src/layout/applyDefaultStyles.js +++ b/packages/textkit/src/layout/applyDefaultStyles.js @@ -33,6 +33,7 @@ const applyAttributes = a => ({ strikeColor: a.strikeColor || a.color || 'black', strikeStyle: a.strikeStyle || 'solid', stroke: a.stroke || false, + verticalAlign: a.verticalAlign || null, wordSpacing: a.wordSpacing || 0, yOffset: a.yOffset || 0, }); diff --git a/packages/textkit/src/layout/index.js b/packages/textkit/src/layout/index.js index b230db5c2..534917bca 100644 --- a/packages/textkit/src/layout/index.js +++ b/packages/textkit/src/layout/index.js @@ -9,6 +9,7 @@ import splitParagraphs from './splitParagraphs'; import finalizeFragments from './finalizeFragments'; import resolveAttachments from './resolveAttachments'; import applyDefaultStyles from './applyDefaultStyles'; +import verticalAlignment from './verticalAlign'; /** * A LayoutEngine is the main object that performs text layout. @@ -28,6 +29,7 @@ const layoutEngine = engines => (attributedString, container, options = {}) => { resolveYOffset(engines, options), resolveAttachments(engines, options), generateGlyphs(engines, options), + verticalAlignment(options), wrapWords(engines, options), ); diff --git a/packages/textkit/src/layout/verticalAlign.js b/packages/textkit/src/layout/verticalAlign.js new file mode 100644 index 000000000..dc0c2dc3e --- /dev/null +++ b/packages/textkit/src/layout/verticalAlign.js @@ -0,0 +1,24 @@ +/* eslint-disable no-restricted-syntax */ + +/** + * Apply scaling and yOffset for verticalAlign 'sub' and 'super'. + * + * @param {Object} layout options + * @param {Object} attributed string + * @return {Object} attributed string + */ +const verticalAlignment = () => attributedString => { + attributedString.runs.forEach(run => { + const { attributes } = run; + const { verticalAlign } = attributes; + + if (verticalAlign === 'sub') { + attributes.yOffset = -0.2; + } else if (verticalAlign === 'super') { + attributes.yOffset = 0.4; + } + }); + return attributedString; +}; + +export default verticalAlignment; diff --git a/packages/textkit/tests/layout/applyDefaultStyles.test.js b/packages/textkit/tests/layout/applyDefaultStyles.test.js index 215355184..3f565c89f 100644 --- a/packages/textkit/tests/layout/applyDefaultStyles.test.js +++ b/packages/textkit/tests/layout/applyDefaultStyles.test.js @@ -32,6 +32,7 @@ const DEFAULTS = { strikeColor: 'black', strikeStyle: 'solid', stroke: false, + verticalAlign: null, wordSpacing: 0, yOffset: 0, }; @@ -67,6 +68,7 @@ const OVERRIDES = { strikeColor: 'red', strikeStyle: 'dashed', stroke: true, + verticalAlign: 'super', wordSpacing: 5, yOffset: 5, }; diff --git a/packages/textkit/tests/layout/verticalAlign.test.js b/packages/textkit/tests/layout/verticalAlign.test.js new file mode 100644 index 000000000..22e3090a9 --- /dev/null +++ b/packages/textkit/tests/layout/verticalAlign.test.js @@ -0,0 +1,56 @@ +import verticalAlignment from '../../src/layout/verticalAlign'; + +describe('verticalAlign', () => { + test('should apply vertical alignment "super" to string', () => { + const instance = verticalAlignment(); + const text = { + string: 'Lorem', + runs: [ + { + start: 0, + end: 5, + attributes: { fontSize: 12, color: 'red', verticalAlign: 'super' }, + }, + ], + }; + + const result = instance(text); + // expect yOffset to be 0.4 + expect(result.runs[0].attributes).toHaveProperty('yOffset', 0.4); + }); + test('should apply vertical alignment "sub" to string', () => { + const instance = verticalAlignment(); + const text = { + string: 'Lorem', + runs: [ + { + start: 0, + end: 5, + attributes: { fontSize: 12, color: 'red', verticalAlign: 'sub' }, + }, + ], + }; + + const result = instance(text); + // expect yOffset to be 0.4 + expect(result.runs[0].attributes).toHaveProperty('yOffset', -0.2); + }); + + test('should ignore vertical alignment value if it is not "sub" or "super"', () => { + const instance = verticalAlignment(); + const text = { + string: 'Lorem', + runs: [ + { + start: 0, + end: 5, + attributes: { fontSize: 12, color: 'red', verticalAlign: 'center' }, + }, + ], + }; + + const result = instance(text); + // expect yOffset property to be undefined + expect(result.runs[0].attributes).not.toHaveProperty('yOffset'); + }); +}); diff --git a/packages/types/style.d.ts b/packages/types/style.d.ts index a30b50cbd..ce050542f 100644 --- a/packages/types/style.d.ts +++ b/packages/types/style.d.ts @@ -58,6 +58,7 @@ export interface Style { textIndent?: any; // ? textOverflow?: 'ellipsis'; textTransform?: 'capitalize' | 'lowercase' | 'uppercase'; + verticalAlign?: 'sub' | 'super'; // Sizing/positioning