UNPKG

@peergrade/react-pdf

Version:

Display PDFs in your React app as easily as if they were images.

162 lines (130 loc) 4.4 kB
import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import PageContext from '../PageContext'; import { isPage, isRotate } from '../shared/propTypes'; // Render disproportion above which font will be considered broken and fallback will be used const BROKEN_FONT_ALARM_THRESHOLD = 0.1; export class TextLayerItemInternal extends PureComponent { state = { transform: null, } componentDidMount() { this.alignTextItem(); } get unrotatedViewport() { const { page, scale } = this.props; return page.getViewport(scale); } /** * It might happen that the page is rotated by default. In such cases, we shouldn't rotate * text content. */ get rotate() { const { page, rotate } = this.props; return rotate - page.rotate; } get sideways() { const { rotate } = this; return rotate % 180 !== 0; } get defaultSideways() { const { rotation } = this.unrotatedViewport; return rotation % 180 !== 0; } get fontSize() { const { transform } = this.props; const { defaultSideways } = this; const [fontHeightPx, fontWidthPx] = transform; return defaultSideways ? fontWidthPx : fontHeightPx; } get top() { const { transform } = this.props; const { unrotatedViewport: viewport, defaultSideways } = this; const [/* fontHeightPx */, /* fontWidthPx */, offsetX, offsetY, x, y] = transform; const [/* xMin */, yMin, /* xMax */, yMax] = viewport.viewBox; return defaultSideways ? x + offsetX + yMin : yMax - (y + offsetY); } get left() { const { transform } = this.props; const { unrotatedViewport: viewport, defaultSideways } = this; const [/* fontHeightPx */, /* fontWidthPx */, /* offsetX */, /* offsetY */, x, y] = transform; const [xMin] = viewport.viewBox; return defaultSideways ? y - xMin : x - xMin; } async getFontData(fontFamily) { const { page } = this.props; const font = await page.commonObjs.ensureObj(fontFamily); return font.data; } async alignTextItem() { if (!this.item) { return; } const element = this.item; element.style.transform = ''; const { fontName, scale, width } = this.props; const targetWidth = width * scale; const fontData = await this.getFontData(fontName); let actualWidth = this.getElementWidth(element); const widthDisproportion = Math.abs((targetWidth / actualWidth) - 1); const repairsNeeded = widthDisproportion > BROKEN_FONT_ALARM_THRESHOLD; if (repairsNeeded) { const fallbackFontName = fontData ? fontData.fallbackName : 'sans-serif'; element.style.fontFamily = fallbackFontName; actualWidth = this.getElementWidth(element); } const ascent = fontData ? fontData.ascent : 1; this.setState({ transform: `scaleX(${targetWidth / actualWidth}) translateY(${(1 - ascent) * 100}%)`, }); } getElementWidth = (element) => { const { sideways } = this; return element.getBoundingClientRect()[sideways ? 'height' : 'width']; }; render() { const { fontSize, top, left } = this; const { fontName, scale, str: text } = this.props; const { transform } = this.state; return ( <div style={{ height: '1em', fontFamily: fontName, fontSize: `${fontSize * scale}px`, position: 'absolute', top: `${top * scale}px`, left: `${left * scale}px`, transformOrigin: 'left bottom', whiteSpace: 'pre', pointerEvents: 'all', transform, }} ref={(ref) => { this.item = ref; }} > { this.props.customTextRenderer ? this.props.customTextRenderer(this.props) : text } </div> ); } } TextLayerItemInternal.propTypes = { customTextRenderer: PropTypes.func, fontName: PropTypes.string.isRequired, itemIndex: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types page: isPage.isRequired, rotate: isRotate, scale: PropTypes.number, str: PropTypes.string.isRequired, transform: PropTypes.arrayOf(PropTypes.number).isRequired, width: PropTypes.number.isRequired, }; const TextLayerItem = props => ( <PageContext.Consumer> {context => <TextLayerItemInternal {...context} {...props} />} </PageContext.Consumer> ); export default TextLayerItem;