Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[react pdf] scss를 적용합니다. #32

Merged
merged 12 commits into from
May 17, 2024
Merged
18 changes: 18 additions & 0 deletions packages/react-pdf/src/components/PDFViewer.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.article {
padding: 7px;
min-height: 100%;
background-color: #f0f2f4;
box-sizing: border-box;

.document {
overflow: hidden;
max-width: 568px;
margin: 0 auto;
background-color: #fff;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2);

& + .document {
margin-top: 7px;
}
}
}
40 changes: 19 additions & 21 deletions packages/react-pdf/src/components/Pages.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import {PropsWithChildren, memo, useCallback, useMemo, useState} from 'react'
import {ReactNode, memo, useCallback, useMemo, useState} from 'react'

import classNames from 'classnames/bind'

import {PdfPageProvider} from '../contexts/page'
import {usePdfContext} from '../contexts/pdf'
import useInfiniteScroll from '../hooks/useInfiniteScroll'
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect'
Expand All @@ -8,19 +11,12 @@ import {AnnotationLayer} from './layer/Annotation'
import {TextLayer} from './layer/Text'
import {PageCanvas} from './page/Canvas'
import {PageSvg} from './page/Svg'
import styles from './PDFViewer.module.scss'

export interface PagesProps {
renderMode?: 'canvas' | 'svg'
lazyLoading?: boolean
tokenize?: boolean
}
const cx = classNames.bind(styles)

export const Page = memo(function Page({
renderMode,
tokenize,
pageNumber,
}: Omit<PagesProps, 'lazyLoading'> & {pageNumber: number}) {
const {pdf} = usePdfContext()
export const Page = memo(function Page({pageNumber}: {pageNumber: number}) {
const {pdf, renderMode} = usePdfContext()
const [page, setPage] = useState<PDFPageProxy | undefined>()

useIsomorphicLayoutEffect(() => {
Expand All @@ -36,17 +32,19 @@ export const Page = memo(function Page({
}

return (
<div style={{position: 'relative'}} data-page-number={pageNumber}>
{renderMode === 'canvas' && <PageCanvas page={page} />}
{renderMode === 'svg' && <PageSvg page={page} />}
<TextLayer page={page} tokenize={tokenize} />
<AnnotationLayer page={page} />
</div>
<PdfPageProvider page={page}>
<div className={cx('document')} style={{position: 'relative'}} data-page-number={pageNumber}>
{renderMode === 'canvas' && <PageCanvas />}
{renderMode === 'svg' && <PageSvg />}
<TextLayer />
<AnnotationLayer />
</div>
</PdfPageProvider>
)
})

export const Pages = memo(function Pages({renderMode, lazyLoading, tokenize, children}: PropsWithChildren<PagesProps>) {
const {pdf} = usePdfContext()
export const Pages = memo(function Pages({children}: {children?: ReactNode}) {
const {pdf, lazyLoading} = usePdfContext()
const pageNumbers = useMemo(() => Array.from({length: pdf.numPages}, (_, index) => index + 1), [pdf.numPages])
const [renderPages, setRenderPages] = useState<number[]>(pdf.numPages > 0 ? [1] : [])

Expand All @@ -66,7 +64,7 @@ export const Pages = memo(function Pages({renderMode, lazyLoading, tokenize, chi
{(lazyLoading ? renderPages : pageNumbers).map((pageNumber) => {
return (
<div key={pageNumber} ref={lazyLoading && renderPages.length === pageNumber ? ref : null}>
<Page renderMode={renderMode} pageNumber={pageNumber} tokenize={tokenize} />
<Page pageNumber={pageNumber} />
</div>
)
})}
Expand Down
80 changes: 53 additions & 27 deletions packages/react-pdf/src/components/PdfViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,55 @@
import {MouseEventHandler, ReactNode, useCallback, useState} from 'react'
import {MouseEventHandler, ReactNode, useCallback, useEffect, useRef, useState} from 'react'

import {PDFProvider} from '../contexts/pdf'
import classNames from 'classnames/bind'

import {PdfProvider, PdfProviderContext} from '../contexts/pdf'
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect'
import usePdfViewerPageWidth from '../hooks/usePdfViewerPageWidth'
import {PDFDocumentProxy} from '../pdfjs-dist/types/pdfjs'
import {getPdfDocument} from '../utils/pdf'
import {Pages, PagesProps} from './Pages'
import {Pages} from './Pages'
import styles from './PDFViewer.module.scss'

const cx = classNames.bind(styles)

type PdfRenderProps = Omit<PdfProviderContext, 'pdf'>

export type PDFViewerProps = PagesProps & {
export type PDFViewerProps = PdfRenderProps & {
/**
* pdf load 시 필요한 props
*/
pdfUrl: string
header?: ReactNode
footer?: ReactNode
tokenize?: boolean
cMapUrl?: string
cMapCompressed?: boolean
withCredentials?: boolean
/**
* pdf viewer custom props
*/
onClickWords?: {target: string | RegExp; callback: () => void | Promise<void>}[]
/**
* pdf load 및 rendering 관련 callback
*/
onLoadPDFRender?: () => void
onErrorPDFRender?: (e: Error) => void
options?: {
cMapUrl?: string
cMapCompressed?: boolean
lazyLoading?: boolean
withCredentials?: boolean
externalLinkTarget?: '_self' | '_blank' | '_parent' | '_top'
}
/**
* pdf 외 rendering 할 컴포넌트
*/
header?: ReactNode
footer?: ReactNode
}

export function PDFViewer({
pdfUrl,
renderMode = 'canvas',
tokenize,
tokenize: injectedTokenize,
onClickWords,
header,
footer,
options,
lazyLoading = true,
...options
}: PDFViewerProps) {
const [pdf, setPdf] = useState<PDFDocumentProxy | undefined>()
const ref = useRef<HTMLDivElement>(null)
const {width, getClientWidth} = usePdfViewerPageWidth(ref)

useIsomorphicLayoutEffect(() => {
async function init() {
Expand All @@ -55,6 +72,13 @@ export function PDFViewer({
init()
}, [options?.cMapCompressed, options?.cMapUrl, options?.withCredentials, pdf, pdfUrl])

useEffect(() => {
if (width) {
return
}
getClientWidth()
}, [width, getClientWidth])

const handleClickWords: MouseEventHandler<HTMLDivElement | HTMLSpanElement> = useCallback(
async (e) => {
if (!onClickWords) {
Expand Down Expand Up @@ -83,21 +107,23 @@ export function PDFViewer({
[onClickWords],
)

if (!pdf) {
if (!pdf || !width) {
return null
}

return (
<div onClick={handleClickWords}>
<PDFProvider pdf={pdf} options={options}>
<PdfProvider
pdf={pdf}
width={width}
lazyLoading={lazyLoading}
tokenize={injectedTokenize ?? (onClickWords || []).length > 0}
{...options}
>
<div ref={ref} className={cx('article')} onClick={handleClickWords}>
{header}
<Pages
renderMode={renderMode}
lazyLoading={options?.lazyLoading || true}
tokenize={tokenize ?? (onClickWords || []).length > 0}
/>
<Pages />
{footer}
</PDFProvider>
</div>
</div>
</PdfProvider>
)
}
27 changes: 15 additions & 12 deletions packages/react-pdf/src/components/layer/Annotation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {memo, useCallback, useState} from 'react'

import classNames from 'classnames/bind'

import {usePdfPageContext} from '../../contexts/page'
import {usePdfContext} from '../../contexts/pdf'
import {useIsomorphicLayoutEffect} from '../../hooks/useIsomorphicLayoutEffect'
// @ts-ignore
Expand All @@ -11,14 +12,10 @@ import * as pdfjs from '../../pdfjs-dist/legacy/build/pdf'
import {PDFLinkService} from '../../pdfjs-dist/lib/web/pdf_link_service'
import styles from './Annotation.module.scss'

import type {PDFAnnotations, PDFPageProxy} from '../../pdfjs-dist/types/pdfjs'
import type {PDFAnnotations} from '../../pdfjs-dist/types/pdfjs'

const cx = classNames.bind(styles)

interface AnnotationLayerProps {
page: PDFPageProxy
}

function getExternalLinkTargetValue(externalLinkTarget?: '_self' | '_blank' | '_parent' | '_top') {
switch (externalLinkTarget) {
case '_self':
Expand All @@ -34,8 +31,9 @@ function getExternalLinkTargetValue(externalLinkTarget?: '_self' | '_blank' | '_
}
}

export const AnnotationLayer = memo(function AnnotationLayer({page}: AnnotationLayerProps) {
const {options} = usePdfContext()
export const AnnotationLayer = memo(function AnnotationLayer() {
const {externalLinkTarget} = usePdfContext()
const {page, scale} = usePdfPageContext()
const [annotations, setAnnotations] = useState<PDFAnnotations | undefined>()

useIsomorphicLayoutEffect(() => {
Expand All @@ -52,10 +50,17 @@ export const AnnotationLayer = memo(function AnnotationLayer({page}: AnnotationL
if (!element) {
return
}

/**
* rerender 전에 해당 layer를 초기화합니다.
*/
const children = element.children
Array.from(children).map((el) => el.remove())

const linkService = new PDFLinkService({
externalLinkTarget: getExternalLinkTargetValue(options?.externalLinkTarget),
externalLinkTarget: getExternalLinkTargetValue(externalLinkTarget),
})
const viewport = page.getViewport({scale: 1}).clone({dontFlip: true})
const viewport = page.getViewport({scale}).clone({dontFlip: true})
const parameters = {
annotations,
div: element,
Expand All @@ -69,8 +74,6 @@ export const AnnotationLayer = memo(function AnnotationLayer({page}: AnnotationL
pdfjs?.AnnotationLayer?.render(parameters)
} catch {}

const children = element.children

if (children.length > 0 && children?.[0]) {
const firstChildren = children[0] as HTMLElement
firstChildren.style.position = 'absolute'
Expand All @@ -89,7 +92,7 @@ export const AnnotationLayer = memo(function AnnotationLayer({page}: AnnotationL
}
})
},
[annotations, options?.externalLinkTarget, page],
[annotations, externalLinkTarget, page, scale],
)

if (!annotations) {
Expand Down
4 changes: 0 additions & 4 deletions packages/react-pdf/src/components/layer/Text.module.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
.text-layer {
position: absolute;
color: transparent;
top: 0;
left: 0;
pointer-events: none;
}

Expand Down
41 changes: 22 additions & 19 deletions packages/react-pdf/src/components/layer/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ import {memo, useCallback, useMemo, useState} from 'react'

import classNames from 'classnames/bind'

import {usePdfPageContext} from '../../contexts/page'
import {usePdfContext} from '../../contexts/pdf'
import {useIsomorphicLayoutEffect} from '../../hooks/useIsomorphicLayoutEffect'
import {mergeTextItems} from '../../utils/text'
import styles from './Text.module.scss'

import type {TextContent, PDFPageProxy, TextContentItem} from '../../pdfjs-dist/types/pdfjs'
import type {TextContent, TextContentItem} from '../../pdfjs-dist/types/pdfjs'

const cx = classNames.bind(styles)

export const TextLayerItem = memo(function TextLayerItem({
textItem: {str: text, transform, fontName, width},
page,
}: {
textItem: TextContentItem

page: PDFPageProxy
}) {
const {rotation, viewBox} = page.getViewport({scale: 1})
const {page, scale} = usePdfPageContext()
const {rotation, viewBox} = page.getViewport({scale})

const drawTextLayerItem = useCallback(
async (element: HTMLSpanElement | null) => {
Expand All @@ -34,7 +34,7 @@ export const TextLayerItem = memo(function TextLayerItem({
element.style.fontFamily = `${fontName}, ${fallbackFontName}`

const defaultSideways = rotation % 180 !== 0
const targetWidth = width
const targetWidth = width * scale
const actualWidth = element.getBoundingClientRect()[defaultSideways ? 'height' : 'width']
let elementTransform = `scaleX(${targetWidth / actualWidth})`
const ascent = fontData?.ascent || 0
Expand All @@ -46,19 +46,22 @@ export const TextLayerItem = memo(function TextLayerItem({
element.style.webkitTransform = elementTransform
})
},
[fontName, page.commonObjs, rotation, width],
[fontName, page.commonObjs, rotation, scale, width],
)

const style = useMemo(() => {
const defaultSideways = rotation % 180 !== 0
const [fontHeightPx, fontWidthPx, offsetX, offsetY, x, y] = transform
const [xMin, yMin, _, yMax] = viewBox
const fontSize = defaultSideways ? fontWidthPx : fontHeightPx
const top = defaultSideways ? x + offsetX + yMin : yMax - (y + offsetY)
const left = defaultSideways ? y - xMin : x - xMin
return {
fontSize: defaultSideways ? fontWidthPx : fontHeightPx,
top: defaultSideways ? x + offsetX + yMin : yMax - (y + offsetY),
left: defaultSideways ? y - xMin : x - xMin,
fontSize: fontSize * scale,
top: top * scale,
left: left * scale,
}
}, [rotation, transform, viewBox])
}, [rotation, scale, transform, viewBox])

return (
<span ref={drawTextLayerItem} className={cx('text-layer-item')} style={style}>
Expand All @@ -67,14 +70,10 @@ export const TextLayerItem = memo(function TextLayerItem({
)
})

interface TextLayerProps {
page: PDFPageProxy
tokenize?: boolean
}

export const TextLayer = memo(function TextLayer({page, tokenize}: TextLayerProps) {
export const TextLayer = memo(function TextLayer() {
const {tokenize} = usePdfContext()
const {page, viewport} = usePdfPageContext()
const [texts, setTexts] = useState<TextContent | undefined>()
const viewport = page.getViewport({scale: 1})

useIsomorphicLayoutEffect(() => {
async function init() {
Expand All @@ -92,12 +91,16 @@ export const TextLayer = memo(function TextLayer({page, tokenize}: TextLayerProp
<div
className={cx('text-layer')}
style={{
position: 'absolute',
color: 'transparent',
top: 0,
left: 0,
width: `${Math.floor(viewport.width)}px`,
height: `${Math.floor(viewport.height)}px`,
}}
>
{texts.items.map((text, index) => (
<TextLayerItem key={index} textItem={text} page={page} />
<TextLayerItem key={index} textItem={text} />
))}
</div>
)
Expand Down
Loading
Loading