PDF.JS 在客户端预览从服务器端发送的大型 pdf 文件(> 200MB)

问题描述 投票:0回答:2

我正在使用 pdf 查看器,它将从服务器(nodejs)加载 pdf 文件,然后在客户端呈现以允许用户直接在我的网站中阅读。

我正在使用 pdf.js 在客户端渲染 pdf 文件。问题是客户端必须下载整个 pdf 文件才能解析和渲染它,因此如果文件太大(在我的情况下约为 200MB),用户必须等待下载整个 200MB。

我研究了一下,我认为我可以通过两种方式解决这个问题:

  • 在服务器端将大型 pdf 文件拆分为许多较小的 pdf 文件,并仅按需提供特定的小文件。但这样,我会丢失一些重要的元数据,例如 pdf 轮廓,...

  • 直接在服务器端使用pdf.js,获取pdf页面,然后将每个页面作为二进制文件提供给客户端,客户端还将使用pdf.js(addPage函数)将每个页面添加到其查看器。但不知道可不可以。

那么我应该怎么做才能解决这个问题呢? 非常感谢。

javascript node.js pdf pdf-generation pdf.js
2个回答
2
投票

最好的解决方案是针对网络优化所有 PDF 文件。

pdf.js
的默认设置将仅加载需要渲染的部分。

请参阅此处了解更多信息。


0
投票

我也面临着同样的问题。对于 2024.09,您可以尝试范围请求并加载 pdf 部分内容,而无需下载整个 pdf。您可以编写部分 api 并实现范围请求或配置静态服务器以支持部分请求。更多信息:https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions#range

这是我正在使用的 api 代码的 Rust 版本(你可以使用任何语言来实现相同的逻辑):

pub fn get_partial_pdf(lastest_pdf: &LatestCompile, range: Option<&HeaderValue>) -> HttpResponse {
    let proj_base_dir = get_proj_base_dir(&lastest_pdf.project_id);
    let pdf_name = format!(
        "{}{}",
        get_filename_without_ext(&lastest_pdf.file_name),
        ".pdf"
    );
    let pdf_file_path = join_paths(&[proj_base_dir, pdf_name]);
    if range.is_none() {
        let mut file = File::open(pdf_file_path).expect("Failed to open file");
        let mut buf = Vec::new();
        file.read_to_end(&mut buf);
        let metadata = file.metadata().expect("Failed to get metadata");
        let file_size = metadata.len();
        return HttpResponse::PartialContent()
            .insert_header(CacheControl(vec![CacheDirective::NoCache]))
            .append_header(("Accept-Ranges", "bytes"))
            .append_header(("Content-Length", file_size))
            .append_header((
                "Access-Control-Expose-Headers",
                "Accept-Ranges,Content-Range",
            ))
            .content_type("application/pdf")
            .body(buf);
    }
    let range_value = range.unwrap().to_str().unwrap();
    warn!("range_value {}", range_value);
    let bytes_info: Vec<&str> = range.unwrap().to_str().unwrap().split("=").collect();
    let mut parts = bytes_info[1].split('-');
    let start = parts.next().unwrap_or("0").parse::<u64>().unwrap_or(0);
    warn!("get the start {}", start);
    let end = parts.next().unwrap_or("0").parse::<u64>().unwrap_or(0);
    warn!("get the end {}", end);
    let mut file = File::open(pdf_file_path).expect("Failed to open file");
    let metadata = file.metadata().expect("Failed to get metadata");
    let file_size = metadata.len();
    file.seek(SeekFrom::Start(start))
        .expect("Failed to seek file");
    let mut buf = vec![0; (end - start + 1) as usize];
    file.take(end - start + 1)
        .read_exact(&mut buf)
        .expect("Failed to read file");
    let content_range = format!("bytes {}-{}/{}", start, end, file_size);
    return HttpResponse::PartialContent()
        .insert_header(CacheControl(vec![CacheDirective::NoCache]))
        .append_header(("Content-Range", content_range))
        .append_header(("Accept-Ranges", "bytes"))
        .append_header(("Content-Length", file_size))
        .append_header((
            "Access-Control-Expose-Headers",
            "Accept-Ranges,Content-Range",
        ))
        .content_type("application/pdf")
        .body(buf);
}

在第一个请求中,客户端将发送一个不带范围标头的请求,服务器返回标头

Accept-Ranges
等并告诉客户端服务器支持范围请求,然后客户端将切换到范围请求以加载其余部分pdf内容。希望这会对您有所帮助。您还可以配置在后端获取整个 pdf 或仅预下载 pdf 的一部分。

© www.soinside.com 2019 - 2024. All rights reserved.