@@ -34,7 +34,16 @@ <h1>docling.rs → wasm: scanned PDF / image, full lite profile</h1>
3434 </ p >
3535 < div id ="drop "> drop a scanned PDF or image here, or click to pick</ div >
3636 < input type ="file " id ="pick " accept =".pdf,.png,.jpg,.jpeg,.tif,.tiff,.webp,.bmp " hidden >
37- < p id ="meta "> loading wasm …</ p >
37+ < p >
38+ Recognition language:
39+ < select id ="lang ">
40+ < option value ="en " selected > English (en_PP-OCRv3)</ option >
41+ < option value ="cyrillic "> Cyrillic + Latin (cyrillic_PP-OCRv3)</ option >
42+ < option value ="ch "> Chinese + Latin (ch_PP-OCRv3)</ option >
43+ </ select >
44+ < span id ="meta "> </ span >
45+ </ p >
46+
3847 < div id ="out "> </ div >
3948
4049 < script type ="module ">
@@ -47,28 +56,58 @@ <h1>docling.rs → wasm: scanned PDF / image, full lite profile</h1>
4756
4857 const LAYOUT_URL =
4958 "https://github.com/docling-project/docling.rs/releases/download/models-v1/layout_heron_int8.onnx" ;
50- const REC_URL =
51- "https://huggingface.co/SWHL/RapidOCR/resolve/main/PP-OCRv3/en_PP-OCRv3_rec_infer.onnx" ;
52- const DICT_URL =
53- "https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/main/ppocr/utils/en_dict.txt" ;
5459 const SCALE = 2.0 ; // px per PDF point — the native pipeline's RENDER_SCALE
5560
5661 const meta = document . getElementById ( "meta" ) ;
5762 const out = document . getElementById ( "out" ) ;
63+ const lang = document . getElementById ( "lang" ) ;
5864
5965 await init ( ) ;
60- meta . textContent = "fetching models (layout ~165 MB on first visit; browser-cached) …" ;
61- const [ layoutModel , recModel , dict ] = await Promise . all ( [
62- fetch ( LAYOUT_URL , { cache : "force-cache" } ) . then ( ( r ) => r . arrayBuffer ( ) ) ,
63- fetch ( REC_URL , { cache : "force-cache" } ) . then ( ( r ) => r . arrayBuffer ( ) ) ,
64- fetch ( DICT_URL , { cache : "force-cache" } ) . then ( ( r ) => r . text ( ) ) ,
65- ] ) ;
66+ const REC_MODELS = {
67+ en : {
68+ model : "https://huggingface.co/SWHL/RapidOCR/resolve/main/PP-OCRv3/en_PP-OCRv3_rec_infer.onnx" ,
69+ dict : "https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/main/ppocr/utils/en_dict.txt" ,
70+ } ,
71+ cyrillic : {
72+ model : "https://huggingface.co/SWHL/RapidOCR/resolve/main/PP-OCRv3/cyrillic_PP-OCRv3_rec_infer.onnx" ,
73+ dict : "https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/main/ppocr/utils/dict/cyrillic_dict.txt" ,
74+ } ,
75+ ch : {
76+ model : "https://huggingface.co/SWHL/RapidOCR/resolve/main/PP-OCRv3/ch_PP-OCRv3_rec_infer.onnx" ,
77+ dict : "https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/main/ppocr/utils/ppocr_keys_v1.txt" ,
78+ } ,
79+ } ;
80+ const recCache = { } ;
81+ async function recFor ( lang ) {
82+ if ( ! recCache [ lang ] ) {
83+ meta . textContent = `fetching ${ lang } recognition model (~10 MB, cached) ...` ;
84+ const spec = REC_MODELS [ lang ] ;
85+ const [ model , dict ] = await Promise . all ( [
86+ fetch ( spec . model , { cache : "force-cache" } ) . then ( ( r ) => r . arrayBuffer ( ) ) ,
87+ fetch ( spec . dict , { cache : "force-cache" } ) . then ( ( r ) => r . text ( ) ) ,
88+ ] ) ;
89+ const session = await ort . InferenceSession . create ( model , { executionProviders : [ "wasm" ] } ) ;
90+ recCache [ lang ] = {
91+ dict,
92+ rec : {
93+ run : async ( n , h , w , data ) => {
94+ const results = await session . run ( {
95+ [ session . inputNames [ 0 ] ] : new ort . Tensor ( "float32" , data , [ n , 3 , h , w ] ) ,
96+ } ) ;
97+ const t = results [ session . outputNames [ 0 ] ] ;
98+ return { data : t . data , dims : Array . from ( t . dims ) } ;
99+ } ,
100+ } ,
101+ } ;
102+ }
103+ return recCache [ lang ] ;
104+ }
105+ meta . textContent = "fetching layout model (~165 MB on first visit; browser-cached) …" ;
106+ const layoutModel = await fetch ( LAYOUT_URL , { cache : "force-cache" } ) . then ( ( r ) => r . arrayBuffer ( ) ) ;
66107 const layoutSession = await ort . InferenceSession . create ( layoutModel , {
67108 executionProviders : [ "wasm" ] ,
68109 } ) ;
69- const recSession = await ort . InferenceSession . create ( recModel , {
70- executionProviders : [ "wasm" ] ,
71- } ) ;
110+ await recFor ( lang . value ) ;
72111 meta . textContent = "ready — drop a scanned PDF or image" ;
73112
74113 // Interop wrappers docling_wasm expects (see src/scanned.rs / src/ocr.rs).
@@ -81,17 +120,9 @@ <h1>docling.rs → wasm: scanned PDF / image, full lite profile</h1>
81120 return { logits : t ( "logits" ) , boxes : t ( "pred_boxes" ) } ;
82121 } ,
83122 } ;
84- const rec = {
85- run : async ( n , h , w , data ) => {
86- const results = await recSession . run ( {
87- [ recSession . inputNames [ 0 ] ] : new ort . Tensor ( "float32" , data , [ n , 3 , h , w ] ) ,
88- } ) ;
89- const t = results [ recSession . outputNames [ 0 ] ] ;
90- return { data : t . data , dims : Array . from ( t . dims ) } ;
91- } ,
92- } ;
93123
94124 async function handlePdf ( bytes , name ) {
125+ const { dict, rec } = await recFor ( lang . value ) ;
95126 const pdf = await pdfjs . getDocument ( { data : bytes } ) . promise ;
96127 const conv = new ScannedConverter ( dict ) ;
97128 const canvas = document . createElement ( "canvas" ) ;
@@ -114,6 +145,7 @@ <h1>docling.rs → wasm: scanned PDF / image, full lite profile</h1>
114145 const started = performance . now ( ) ;
115146 try {
116147 const bytes = new Uint8Array ( await file . arrayBuffer ( ) ) ;
148+ const { dict, rec } = await recFor ( lang . value ) ;
117149 const md = file . name . toLowerCase ( ) . endsWith ( ".pdf" )
118150 ? await handlePdf ( bytes , file . name )
119151 : await convert_scanned_image ( bytes , file . name , dict , layout , rec , "md" ) ;
0 commit comments