Skip to content

Commit f36d156

Browse files
support wasm standalone
1 parent 6fc08e1 commit f36d156

File tree

4 files changed

+155
-58
lines changed

4 files changed

+155
-58
lines changed

docker/android/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ RUN mkdir /build
5454
WORKDIR /build
5555
RUN gclient config --custom-var checkout_configuration=minimal --unmanaged https://pdfium.googlesource.com/pdfium.git
5656
RUN echo "target_os = [ 'android' ]" >> .gclient
57-
RUN gclient sync -r origin/chromium/7442 --no-history --shallow
57+
RUN gclient sync -r origin/chromium/7590 --no-history --shallow
5858

5959
# pdfium reset and clean directories
6060
RUN git -C /build/pdfium reset --hard

docker/wasm/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ RUN mkdir /build
3838
WORKDIR /build
3939
RUN gclient config --custom-var checkout_configuration=minimal --unmanaged https://pdfium.googlesource.com/pdfium.git
4040
RUN echo "target_os = [ 'emscripten' ]" >> .gclient
41-
RUN gclient sync -r origin/chromium/7442 --no-history --shallow
41+
RUN gclient sync -r origin/chromium/7590 --no-history --shallow
4242

4343
# pdfium reset and clean directories
4444
RUN git -C /build/pdfium reset --hard

modules/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
task = ""
44

55
# pdfium
6-
pdfium_git_branch = "chromium/7442"
7-
# ^ ref: https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/7442
6+
pdfium_git_branch = "chromium/7590"
7+
# ^ ref: https://pdfium.googlesource.com/pdfium/+/refs/heads/chromium/7590
88
# OBS 1: don't forget change in android docker file (docker/android/Dockerfile)
99
# OBS 2: don't forget change in wasm docker file (docker/wasm/Dockerfile)
1010

modules/wasm.py

Lines changed: 151 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -483,64 +483,158 @@ def run_task_test():
483483

484484
# -----------------------------------------------------------------------------
485485
def run_task_test_wasmtime():
486-
import wasmtime
487-
from wasmtime import Engine, Func, Instance, Linker, Module, Store
488-
from wasmtime.loader import WasiConfig
486+
from pathlib import Path
487+
488+
from wasmtime import Engine, FuncType, Linker, Module, Store, WasiConfig
489489

490490
l.colored("Testing with wasmtime...", l.YELLOW)
491491

492492
current_dir = f.current_dir()
493493

494-
for target in c.targets_wasm:
495-
# paths
496-
relative_dir = os.path.join(
497-
"build",
498-
target["target_os"],
499-
target["target_cpu"],
500-
)
494+
for config in c.configurations_wasm:
495+
for target in c.targets_wasm:
496+
l.colored(
497+
'Testing arch "{0}" and configuration "{1}"...'.format(
498+
target["target_cpu"], config
499+
),
500+
l.YELLOW,
501+
)
502+
503+
# paths
504+
relative_dir = os.path.join(
505+
"build",
506+
target["target_os"],
507+
target["target_cpu"],
508+
config,
509+
)
510+
511+
root_dir = os.path.join(current_dir, relative_dir)
512+
node_dir = os.path.join(root_dir, "node")
513+
wasm_file = os.path.join(node_dir, "pdfium.std.wasm")
514+
515+
# check if wasm file exists
516+
if not f.file_exists(wasm_file):
517+
l.e(f"WASM file not found: {wasm_file}")
518+
continue
519+
520+
l.bullet(f"WASM file: {wasm_file}", l.YELLOW)
521+
522+
# create engine and load the pdfium.wasm module
523+
engine = Engine()
524+
module = Module.from_file(engine, wasm_file)
525+
526+
# create WASI context and store
527+
wasi_config = WasiConfig()
528+
wasi_config.inherit_stdin()
529+
wasi_config.inherit_stdout()
530+
wasi_config.inherit_stderr()
531+
532+
store = Store(engine)
533+
store.set_wasi(wasi_config)
534+
535+
# create a linker and add WASI support
536+
linker = Linker(engine)
537+
linker.define_wasi()
538+
539+
# define stub functions for unknown imports
540+
for imp in module.imports:
541+
module_name = imp.module
542+
field_name = imp.name
543+
544+
# check if this is a function import
545+
if isinstance(imp.type, FuncType):
546+
func_type = imp.type
547+
548+
# create a stub function that returns default values
549+
def make_stub(ft):
550+
def stub_func(*_args):
551+
# return default values (0 or None) based on results
552+
if ft.results:
553+
if len(ft.results) == 1:
554+
return 0
555+
return tuple(0 for _ in ft.results)
556+
return None
557+
558+
return stub_func
559+
560+
try:
561+
linker.define_func(
562+
module_name, field_name, func_type, make_stub(func_type)
563+
)
564+
except Exception:
565+
# already defined (e.g., by WASI)
566+
pass
567+
568+
# instantiate the module
569+
instance = linker.instantiate(store, module)
570+
exports = instance.exports(store)
571+
572+
# get and call FPDF_InitLibrary function
573+
try:
574+
init_library = exports["FPDF_InitLibrary"]
575+
init_library(store)
576+
l.bullet("FPDF_InitLibrary successfully called", l.GREEN)
577+
except KeyError:
578+
l.e("Function 'FPDF_InitLibrary' not found")
579+
continue
580+
581+
# test with a sample PDF file
582+
sample_pdf = os.path.join(
583+
current_dir, "sample-wasm", "assets", "web-assembly.pdf"
584+
)
585+
586+
if not f.file_exists(sample_pdf):
587+
l.bullet("Sample PDF not found, skipping document test", l.PURPLE)
588+
continue
589+
590+
l.bullet(f"Testing with PDF: {sample_pdf}", l.YELLOW)
591+
592+
# read PDF data
593+
pdf_path = Path(sample_pdf)
594+
pdf_data = pdf_path.read_bytes()
595+
596+
# allocate memory for the PDF data
597+
malloc = exports["malloc"]
598+
memory = exports["memory"]
599+
600+
# allocate buffer in WASM memory
601+
buf_ptr = malloc(store, len(pdf_data))
602+
mem_data = memory.data_ptr(store)
603+
604+
# copy PDF data to WASM memory
605+
for i, byte in enumerate(pdf_data):
606+
mem_data[buf_ptr + i] = byte
501607

502-
root_dir = os.path.join(current_dir, relative_dir)
503-
gen_dir = os.path.join(root_dir, "gen")
504-
gen_out_dir = os.path.join(gen_dir, "out")
505-
wasm_file = os.path.join(gen_out_dir, "pdfium.std.wasm")
506-
507-
# create the engine and store
508-
engine = Engine()
509-
store = Store(engine)
510-
511-
# load the WebAssembly module
512-
module = Module.from_file(engine, wasm_file)
513-
514-
# create a linker to provide the necessary imports
515-
linker = Linker(engine)
516-
517-
# WASI support using wasmtime.loader.WasiConfig
518-
wasi_config = WasiConfig()
519-
wasi_config.inherit_stdin()
520-
wasi_config.inherit_stdout()
521-
wasi_config.inherit_stderr()
522-
store.set_wasi(wasi_config)
523-
524-
# instantiate the WebAssembly module with linker
525-
instance = linker.instantiate(store, module)
526-
527-
# now we can invoke exported functions from the WebAssembly module
528-
try:
529-
init_library = instance.exports(store)["FPDF_InitLibrary"]
530-
init_library(store)
531-
l.i("FPDF_InitLibrary successfully called.")
532-
except KeyError:
533-
l.e("Function 'FPDF_InitLibrary' not found.")
534-
535-
# invoke 'FPDF_GetLastError'
536-
try:
537-
get_last_error = instance.exports(store)["FPDF_GetLastError"]
538-
result = get_last_error(store)
539-
l.i(f"Result from FPDF_GetLastError: {result}")
540-
except KeyError:
541-
l.e("Function 'FPDF_GetLastError' not found.")
542-
543-
l.i(f"Result: {result}")
608+
# load the PDF document from memory
609+
fpdf_load_mem_document = exports["FPDF_LoadMemDocument"]
610+
doc = fpdf_load_mem_document(store, buf_ptr, len(pdf_data), 0)
611+
612+
if doc == 0:
613+
get_last_error = exports["FPDF_GetLastError"]
614+
error = get_last_error(store)
615+
l.e(f"Failed to load PDF. Error code: {error}")
616+
617+
# free the allocated memory
618+
free = exports["free"]
619+
free(store, buf_ptr)
620+
continue
621+
622+
# get page count
623+
fpdf_get_page_count = exports["FPDF_GetPageCount"]
624+
page_count = fpdf_get_page_count(store, doc)
625+
626+
l.bullet(f"PDF: {pdf_path.name}", l.GREEN)
627+
l.bullet(f"Number of pages: {page_count}", l.GREEN)
628+
629+
# close the document
630+
fpdf_close_document = exports["FPDF_CloseDocument"]
631+
fpdf_close_document(store, doc)
632+
633+
# free the allocated memory
634+
free = exports["free"]
635+
free(store, buf_ptr)
636+
637+
l.bullet("Test completed successfully", l.GREEN)
544638

545639
l.ok()
546640

@@ -655,7 +749,8 @@ def run_task_generate():
655749
"{0}".format("-g" if config == "debug" else "-O2"),
656750
"-s",
657751
f"EXPORTED_FUNCTIONS={complete_functions_list}",
658-
"-s", "ALLOW_TABLE_GROWTH",
752+
"-s",
753+
"ALLOW_TABLE_GROWTH",
659754
"-s",
660755
'EXPORTED_RUNTIME_METHODS=\'["ccall", "cwrap", "wasmExports", "HEAP8", "HEAP16", "HEAP32", "HEAPU8", "HEAPU16", "HEAPU32", "HEAPF32", "HEAPF64", "addFunction", "removeFunction", "setValue"]\'',
661756
"custom.cpp",
@@ -848,7 +943,9 @@ def run_task_archive():
848943
)
849944

850945
# Create per config "npm install"-compatible tarball
851-
per_config_tar = tarfile.open(os.path.join(current_dir, f"wasm-{config}.tgz"), "w:gz")
946+
per_config_tar = tarfile.open(
947+
os.path.join(current_dir, f"wasm-{config}.tgz"), "w:gz"
948+
)
852949
per_config_tar.add(
853950
name=lib_dir,
854951
# Use "package" as the root directory to be compatible with "npm install"

0 commit comments

Comments
 (0)