From 6f0ee0faaa9451e32deeca917c69149b49b25618 Mon Sep 17 00:00:00 2001 From: Luca Foppiano Date: Fri, 16 Aug 2024 11:20:21 +0200 Subject: [PATCH] add scrolling to page or to annotation --- streamlit_pdf_viewer/__init__.py | 78 ++++++++++++++++--- .../frontend/src/PdfViewer.vue | 58 ++++++++------ 2 files changed, 102 insertions(+), 34 deletions(-) diff --git a/streamlit_pdf_viewer/__init__.py b/streamlit_pdf_viewer/__init__.py index ad90a659..deecfa54 100644 --- a/streamlit_pdf_viewer/__init__.py +++ b/streamlit_pdf_viewer/__init__.py @@ -6,7 +6,7 @@ import streamlit.components.v1 as components import json -_RELEASE = True +_RELEASE = False RENDERING_EMBED = "legacy_embed" RENDERING_IFRAME = "legacy_iframe" RENDERING_UNWRAP = "unwrap" @@ -35,7 +35,9 @@ def pdf_viewer(input: Union[str, Path, bytes], rendering: str = RENDERING_UNWRAP, pages_to_render: List[int] = (), render_text: bool = False, - resolution_boost: int = 1 + resolution_boost: int = 1, + scroll_to_page: int = None, + scroll_to_annotation: int = None, ): """ pdf_viewer function to display a PDF file in a Streamlit app. @@ -54,6 +56,8 @@ def pdf_viewer(input: Union[str, Path, bytes], working to implement for the "unwrap" method. :param render_text: Whether to enable selection of text in the PDF viewer. Defaults to False. :param resolution_boost: Boost the resolution by a factor from 2 to 10. Defaults to 1. + :param scroll_to_page: Scroll to a specific page in the PDF. The parameter is an integer, which represent the positional value of the page. E.g. 1, will be the first page. Defaults to None. + :param scroll_to_annotation: Scroll to a specific annotation in the PDF. The parameter is an integer, which represent the positional value of the annotation. E.g. 1, will be the first annotation. Defaults to None. The function reads the PDF file (from a file path, URL, or binary data), encodes it in base64, and uses a Streamlit component to render it in the app. It supports optional annotations and adjustable margins. @@ -74,6 +78,16 @@ def pdf_viewer(input: Union[str, Path, bytes], elif resolution_boost > 10: raise ValueError("ratio_boost must be lower than 10") + if scroll_to_page: + if scroll_to_annotation: + raise ValueError("scroll_to_page and scroll_to_annotation cannot be used together") + if scroll_to_page < 1: + scroll_to_page = None + + else: + if scroll_to_annotation < 1: + scroll_to_annotation = None + if type(input) is not bytes: with open(input, 'rb') as fo: binary = fo.read() @@ -99,13 +113,16 @@ def pdf_viewer(input: Union[str, Path, bytes], rendering=rendering, pages_to_render=pages_to_render, render_text=render_text, - resolution_boost=resolution_boost + resolution_boost=resolution_boost, + scroll_to_page=scroll_to_page, + scroll_to_annotation=scroll_to_annotation ) return component_value if not _RELEASE: import streamlit as st + from streamlit import markdown # from glob import glob @@ -117,6 +134,29 @@ def pdf_viewer(input: Union[str, Path, bytes], # with st.container(height=600): # pdf_viewer(path, width=800, render_text=True, resolution_boost=values[id]) # + # def scroll_to_page(page): + # st.markdown( + # """ + # function(){ + # document.getElementById(""" + page + """).scrollIntoView({behavior: 'smooth'}) + # }; + # + # function() + # """, unsafe_allow_html=True) + + # print(page) + # st.components.v1.html( + # """ + # + # """ + # ) with open("resources/test.pdf", 'rb') as fo: binary = fo.read() @@ -129,20 +169,34 @@ def pdf_viewer(input: Union[str, Path, bytes], tab1, tab2 = st.tabs(["tab1", "tab2"]) + # st.markdown(""" + # """, unsafe_allow_html=True) + # @st.fragment + # def show_buttons_scrolling(pages_id: List): + # for page in pages_id: + # print(page) + # st.button(f"Page {page}", key=f"page_{page}", on_click=scroll_to_page, args=(page,)) + with tab1: st.markdown("tab 1") - with st.container(height=300): + with st.container(height=400): viewer = pdf_viewer( binary, annotations=annotations, render_text=True, - key="bao" + key="bao", + scroll_to_page=3 ) - st.markdown(viewer) - st.markdown(type(viewer)) - - annotations_id = viewer['annotations'] - pages_id = viewer['pages'] + # st.markdown(viewer) + # st.markdown(type(viewer)) + # if type(viewer) == dict: + # annotations_id = viewer['annotations'] + # pages_id = viewer['pages'] + # show_buttons_scrolling(pages_id) with tab2: st.markdown("tab 2") @@ -152,5 +206,7 @@ def pdf_viewer(input: Union[str, Path, bytes], annotations=annotations, render_text=True, key="miao", - resolution_boost=4 + resolution_boost=4, + scroll_to_annotation=2 ) + diff --git a/streamlit_pdf_viewer/frontend/src/PdfViewer.vue b/streamlit_pdf_viewer/frontend/src/PdfViewer.vue index 879820e1..38d7c1c8 100644 --- a/streamlit_pdf_viewer/frontend/src/PdfViewer.vue +++ b/streamlit_pdf_viewer/frontend/src/PdfViewer.vue @@ -4,8 +4,7 @@
- -
+
@@ -97,8 +96,7 @@ export default { 'z-index': 10 }; if (index) { - obj['id'] = `annotation-${index}`; - loadedAnnotations.value.push(obj['id']) + loadedAnnotations.value.push(`annotation-${index}`); } return obj }; @@ -232,7 +230,6 @@ export default { totalHeight.value += canvas.height / ratio await renderPage(page, canvas, viewport) if (canvas.id !== undefined) { - console.log(canvas.id) loadedPages.value.push(canvas.id) } } @@ -266,6 +263,36 @@ export default { } }; + const scrollToItem = () => { + if (props.args.scroll_to_page) { + const page = document.getElementById(`canvas_page_${props.args.scroll_to_page}`); + if (page) { + page.scrollIntoView({behavior: "smooth"}); + } + } else if (props.args.scroll_to_annotation) { + const annotation = document.getElementById(`annotation-${props.args.scroll_to_annotation}`); + if (annotation) { + annotation.scrollIntoView({behavior: "smooth", block: "center"}); + } + } + }; + + const collectAndReturnIds = () => { + const pages_ids = new Set() + const annotations_ids = new Set() + + let j + + for (j = 0; j < loadedAnnotations.value.length; j++) { + annotations_ids.add(loadedAnnotations.value[j]); + } + for (j = 0; j < loadedPages.value.length; j++) { + pages_ids.add(loadedPages.value[j]); + } + + Streamlit.setComponentValue({"pages": Array.from(pages_ids), "annotations": Array.from(annotations_ids)}) + } + const setFrameHeight = () => { Streamlit.setFrameHeight(props.args.height || totalHeight.value); @@ -293,24 +320,8 @@ export default { if (props.args.rendering === "unwrap") { loadPdfs(binaryDataUrl) .then(setFrameHeight) - .then(Streamlit.setComponentReady) - .then( - function() { - const pages_ids = new Set() - const annotations_ids = new Set() - - let j = 0 - - for(j=0; j < loadedAnnotations.value.length; j++) { - annotations_ids.add(loadedAnnotations.value[j]); - } - for(j=0; j < loadedPages.value.length; j++) { - pages_ids.add(loadedPages.value[j]); - } - - Streamlit.setComponentValue({"pages": Array.from(pages_ids), "annotations": Array.from(annotations_ids)}) - } - ); + .then(collectAndReturnIds) + .then(Streamlit.setComponentReady); } else { setFrameHeight(); Streamlit.setComponentReady(); @@ -319,6 +330,7 @@ export default { onUpdated(() => { setFrameHeight(); + scrollToItem(); });