Skip to content

Commit

Permalink
Merge pull request #23 from lfoppiano/feature/selective-pdf-rendering
Browse files Browse the repository at this point in the history
Render selected PDF pages only
  • Loading branch information
lfoppiano authored Feb 13, 2024
2 parents b50a406 + ab70804 commit 667495f
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 21 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ In the following table the list of parameters that can be provided to the `pdf_v
| pages_vertical_spacing | The vertical space (in pixels) between each page of the PDF. Defaults to 2 pixels. |
| annotation_outline_size | Size of the outline around each annotation in pixels. Defaults to 1 pixel. |
| rendering | Type of rendering: `unwrap` (default), `legacy_iframe`, or `legacy_embed`. The default value, `unwrap` shows the PDF document using pdf.js, and supports the visualisation of annotations. Other values are `legacy_iframe` and `legacy_embed` which use the legacy approach of injecting the document into an `<embed>` or `<iframe>`. These methods enable the default pdf viewer of Firefox/Chrome/Edge that contains additional features we are still working to implement in this component. **NOTE**: Annotations are ignored for both 'legacy_iframe' and 'legacy_embed'. |
| pages_to_render | Optional list of page numbers to render. If None, all pages are rendered. This parameter allows for selective rendering of pages in the PDF, which can be particularly useful for large documents or when only specific pages are required. |

## Developers notes

Expand Down
11 changes: 8 additions & 3 deletions streamlit_pdf_viewer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import base64
import os
from pathlib import Path
from typing import Union
from typing import Union, List, Optional

import streamlit.components.v1 as components
import json
Expand All @@ -26,7 +26,8 @@ def pdf_viewer(input: Union[str, Path, bytes], width: int = 700, height: int = N
annotations=(),
pages_vertical_spacing: int = 2,
annotation_outline_size: int = 1,
rendering: str = "unwrap"
rendering: str = "unwrap",
pages_to_render: Optional[List[int]] = None
):
"""
pdf_viewer function to display a PDF file in a Streamlit app.
Expand All @@ -39,6 +40,7 @@ def pdf_viewer(input: Union[str, Path, bytes], width: int = 700, height: int = N
:param pages_vertical_spacing: The vertical space (in pixels) between each page of the PDF. Defaults to 2 pixels.
:param annotation_outline_size: Size of the outline around each annotation in pixels. Defaults to 1 pixel.
:param rendering: Type of rendering. The default is "unwrap", which unwrap the PDF. Other values are
:param pages_to_render: Optional list of page numbers to render. If None, all pages are rendered. This allows for selective rendering of pages in the PDF.
"legacy_iframe" and "legacy_embed" which uses the legacy approach for showing PDF document with streamlit.
These methods enable the default pdf viewer of Firefox/Chrome/Edge that contains additional features we are still
working to implement for the "unwrap" method.
Expand All @@ -54,6 +56,8 @@ def pdf_viewer(input: Union[str, Path, bytes], width: int = 700, height: int = N
raise TypeError("Width must be an integer")
if height is not None and not isinstance(height, int):
raise TypeError("Height must be an integer or None")
if pages_to_render is not None and not all(isinstance(page, int) for page in pages_to_render):
raise TypeError("pages_to_render must be a list of integers or None")

if type(input) is not bytes:
with open(input, 'rb') as fo:
Expand All @@ -71,7 +75,8 @@ def pdf_viewer(input: Union[str, Path, bytes], width: int = 700, height: int = N
annotations=annotations,
pages_vertical_spacing=pages_vertical_spacing,
annotation_outline_size=annotation_outline_size,
rendering=rendering
rendering=rendering,
pages_to_render=pages_to_render
)
return component_value

Expand Down
62 changes: 44 additions & 18 deletions streamlit_pdf_viewer/frontend/src/PdfViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div v-if="args.rendering==='unwrap'">
<div id="pdfViewer" :style="pdfViewerStyle">
<div id="pdfAnnotations" v-if="args.annotations">
<div v-for="(annotation, index) in args.annotations" :key="index" :style="getPageStyle">
<div v-for="(annotation, index) in filteredAnnotations" :key="index" :style="getPageStyle">
<div :style="getAnnotationStyle(annotation)" :id="`annotation-${index}`"></div>
</div>
</div>
Expand Down Expand Up @@ -36,6 +36,17 @@ export default {
const pageScales = ref([]);
const pageHeights = ref([]);
const isRenderingAllPages = props.args.pages_to_render.length === 0;
const filteredAnnotations = computed(() => {
if (isRenderingAllPages) {
return props.args.annotations;
}
const filteredAnnotations = props.args.annotations.filter(anno =>{
return props.args.pages_to_render.includes(Number(anno.page))
})
return filteredAnnotations;
});
const pdfContainerStyle = computed(() => ({
width: `${props.args.width}px`,
Expand All @@ -47,15 +58,21 @@ export default {
const getPageStyle = {position: 'relative'};
const calculatePdfsHeight = (page) => {
const pageIndex = page - 1;
let height = 0;
for (let i = 0; i < pageIndex; i++) {
height += Math.floor(pageHeights.value[i] * pageScales.value[i]) + props.args.pages_vertical_spacing; // Add margin for each page
if (isRenderingAllPages) {
for (let i = 0; i < page - 1; i++) {
height += Math.floor(pageHeights.value[i] * pageScales.value[i]) + props.args.pages_vertical_spacing; // Add margin for each page
}
} else {
for (let i = 0; i < pageHeights.value.length; i++) {
if (props.args.pages_to_render.includes(i + 1) && i < page - 1) {
height += Math.floor(pageHeights.value[i] * pageScales.value[i]) + props.args.pages_vertical_spacing;
}
}
}
return height;
};
const getAnnotationStyle = (annoObj) => {
const scale = pageScales.value[annoObj.page - 1];
return {
Expand Down Expand Up @@ -97,30 +114,38 @@ export default {
await renderTask.promise;
};
const renderPdfPages = async (pdf, pdfViewer) => {
const renderPdfPages = async (pdf, pdfViewer, pagesToRender = null) => {
if (pagesToRender.length === 0) {
pagesToRender = []
for (let i = 0; i < pdf.numPages; i++) {
pagesToRender.push(i + 1);
}
}
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const rotation = page.rotate;
const actualViewport = page.getViewport({scale: 1.0, rotation: rotation});
const scale = props.args.width / actualViewport.width;
pageScales.value.push(scale);
pageHeights.value.push(actualViewport.height);
const canvas = createCanvasForPage(page, scale, rotation);
pdfViewer?.append(canvas);
if (canvas.width > maxWidth.value) {
maxWidth.value = canvas.width;
if (pagesToRender.includes(i)) {
const canvas = createCanvasForPage(page, scale, rotation);
pdfViewer?.append(canvas);
if (canvas.width > maxWidth.value) {
maxWidth.value = canvas.width;
}
totalHeight.value += canvas.height;
totalHeight.value += props.args.pages_vertical_spacing;
await renderPage(page, canvas);
}
totalHeight.value += canvas.height;
totalHeight.value += props.args.pages_vertical_spacing;
await renderPage(page, canvas);
}
// Subtract the margin for the last page as it's not needed
totalHeight.value -= props.args.pages_vertical_spacing;
if (pagesToRender.length > 0) {
totalHeight.value -= props.args.pages_vertical_spacing;
}
};
const alertError = (error) => {
window.alert(error.message);
console.error(error);

Check warning on line 151 in streamlit_pdf_viewer/frontend/src/PdfViewer.vue

View workflow job for this annotation

GitHub Actions / build (3.7, 18)

Unexpected console statement

Check warning on line 151 in streamlit_pdf_viewer/frontend/src/PdfViewer.vue

View workflow job for this annotation

GitHub Actions / build (3.8, 18)

Unexpected console statement

Check warning on line 151 in streamlit_pdf_viewer/frontend/src/PdfViewer.vue

View workflow job for this annotation

GitHub Actions / build (3.9, 18)

Unexpected console statement

Check warning on line 151 in streamlit_pdf_viewer/frontend/src/PdfViewer.vue

View workflow job for this annotation

GitHub Actions / build (3.10, 18)

Unexpected console statement

Check warning on line 151 in streamlit_pdf_viewer/frontend/src/PdfViewer.vue

View workflow job for this annotation

GitHub Actions / build (3.11, 18)

Unexpected console statement
Expand All @@ -133,7 +158,7 @@ export default {
clearExistingCanvases(pdfViewer);
const pdf = await loadingTask.promise;
await renderPdfPages(pdf, pdfViewer);
await renderPdfPages(pdf, pdfViewer, props.args.pages_to_render);
} catch (error) {
alertError(error);
}
Expand All @@ -156,6 +181,7 @@ export default {
onUpdated(setFrameHeight);
return {
filteredAnnotations,
getAnnotationStyle,
pdfContainerStyle,
pdfViewerStyle,
Expand Down

0 comments on commit 667495f

Please sign in to comment.