首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

自制PDF阅读器

我们可以使用 go-fitz 很轻易的制作一款 pdf 文档阅读器,原理是使用 go-fitz 将 pdf 转换为 html 内容,然后使用 go 搭建一个 web 服务,之后再通过浏览器进行文档阅读。相比直接使用浏览器阅读 pdf 文档的好处是原本一些不支持在 pdf 文档中使用的浏览器插件变得可以使用了,我们可以很轻松在阅读 pdf 文档时使用文本翻译,文本语音合成,GPT文章总结……这些功能。

本教程将引导你使用 Go 和 go-fitz 库构建一个 PDF 阅读器。go-fitz 是一个流行的 Go 库,它允许你操作 PDF 文档,包括将它们转换为 HTML。

1. 导入依赖项

首先,你需要导入必要的依赖项:

import (

"flag"

"fmt"

"log"

"net/http"

"os"

"os/signal"

"strconv"

"syscall"

"github.com/gen2brain/go-fitz"

)

flag:用于处理命令行参数。

fmt:用于格式化和打印输出。

log:用于记录信息。

net/http:用于创建 Web 服务器。

os:用于处理文件。

os/signal:用于处理操作系统信号。

strconv:用于将字符串转换为其他数据类型。

syscall:用于处理系统调用。

github.com/gen2brain/go-fitz:go-fitz 库。

2. 定义常量和模板

接下来,定义一个 HTML 框架和一个常量:

const frame = `

body{background-color:slategray}

div{position:relative;background-color:white;margin:1em auto;box-shadow:1px 1px 8px -2px black}

div#pages{background-color:#0000;margin:0;box-shadow:none}

p{position:absolute;white-space:pre;margin:0}

async function load_page(position, page_ix=-1) {

const pages = document.querySelector('#pages');

if (page_ix == -1) {

if (position == 'beforeEnd') {

page_ix = Number(

pages.lastElementChild.id.substr('page'.length)) + 1;

} else if (position == 'afterBegin') {

page_ix = Number(

pages.firstElementChild.id.substr('page'.length)) - 1;

}

}

const url = '/page?ix=' + page_ix;

const resp = await fetch(url);

if (!resp.ok) {

return;

}

const page = await resp.text()

pages.insertAdjacentHTML(position, page);

if (position == 'afterBegin') {

window.scrollTo(0, pages.firstChild.offsetHeight);

}

}

function load_more() {

const autoload = async () => {

if ((window.innerHeight + window.scrollY) >= document.body.parentNode.offsetHeight) {

window.removeEventListener("scroll", autoload);

console.log("loading page to end...");

await load_page('beforeEnd');

window.addEventListener("scroll", autoload);

} else if (window.scrollY == 0) {

window.removeEventListener("scroll", autoload);

console.log("loading page to begin...");

await load_page('afterBegin');

window.addEventListener("scroll", autoload);

}

}

window.addEventListener("scroll", autoload);

}

window.onload = async () => {

const page_ix = (new URLSearchParams(location.search)).get('start') || 0;

await load_page('beforeEnd', page_ix);

await load_page('afterBegin', page_ix - 1);

load_more();

}

`

frame:这是网页的 HTML 框架。它定义了页面的布局、样式和包含 PDF 内容的容器。

pages:这是将 PDF 页面插入的 HTML 容器的 ID。

3. 处理命令行参数

接下来,从命令行参数获取 PDF 文件名:

filename := flag.String("filename", "", "pdf filename")

flag.Parse()

这会获取一个名为 --filename 的命令行参数,它表示要打开的 PDF 文件的路径。

4. 打开 PDF 文档

使用 go-fitz 打开 PDF 文档:

var doc *fitz.Document

doc, err = fitz.New(*filename)

if err != nil {

log.Fatal(err)

}

这会打开给定路径的 PDF 文档并将其存储在 doc 变量中。

5. 处理系统信号

设置一个信号通道来监听 SIGINT 信号(通常是按 Ctrl+C 触发):

signal_chan := make(chan os.Signal, 1)

signal.Notify(signal_chan, syscall.SIGINT)

并设置一个 goroutine 来处理信号并关闭 PDF 文档:

go func() {

<-signal_chan

if doc != nil {

doc.Close()

}

os.Exit(0)

}()

这将确保在收到 SIGINT 信号时关闭 PDF 文档并退出程序。

6. 处理页面请求

设置一个 HTTP 处理程序来处理 /page 路由,它将提供 PDF 的单个页面:

func page_handler(w http.ResponseWriter, r *http.Request) {

values := r.URL.Query()

page_ix, err := strconv.Atoi(values.Get("ix"))

if err != nil {

http.Error(w, "page number valid faild", http.StatusBadRequest)

return

}

log.Println("page ix:", page_ix)

if page_ix < 0 || page_ix >= doc.NumPage() {

http.Error(w, "load page faild", http.StatusNotFound)

return

}

html, err := doc.HTML(page_ix, false)

if err != nil {

http.Error(w, "load page faild", http.StatusInternalServerError)

return

}

fmt.Fprint(w, html)

}

这会获取请求的页面索引 (ix 查询参数),验证索引是否有效,然后使用 go-fitz 将页面转换为 HTML 并发送给客户端。

7. 处理首页请求

设置一个 HTTP 处理程序来处理 / 路由,它将提供带有 PDF 内容的 HTML 框架:

func index_handler(w http.ResponseWriter, r *http.Request) {

fmt.Fprint(w, frame)

}

这会将 frame 框架发送给客户端,其中包含用于加载 PDF 页面的脚本。

8. 启动 Web 服务器

最后,启动一个在端口 8000 上监听的 Web 服务器:

http.HandleFunc("/page", page_handler)

http.HandleFunc("/", index_handler)

log.Fatal(http.ListenAndServe(":8000", nil))

这将启动服务器并允许客户端通过浏览器访问 PDF 内容。

9. 使用 PDF 阅读器

现在,你可以通过访问 http://localhost:8000 在浏览器中打开 PDF 阅读器。该页面将显示 PDF 的第一页。你可以使用滚动条浏览页面,或者使用键盘快捷键(如 和 )进行导航。你还可以使用浏览器的插件,如翻译器和语音合成,来增强阅读体验。

完整示例:

package main

import (

"flag"

"fmt"

"log"

"net/http"

"os"

"os/signal"

"strconv"

"syscall"

"github.com/gen2brain/go-fitz"

)

var doc *fitz.Document

const frame = `

body{background-color:slategray}

div{position:relative;background-color:white;margin:1em auto;box-shadow:1px 1px 8px -2px black}

div#pages{background-color:#0000;margin:0;box-shadow:none}

p{position:absolute;white-space:pre;margin:0}

async function load_page(position, page_ix=-1) {

const pages = document.querySelector('#pages');

if (page_ix == -1) {

if (position == 'beforeEnd') {

page_ix = Number(

pages.lastElementChild.id.substr('page'.length)) + 1;

} else if (position == 'afterBegin') {

page_ix = Number(

pages.firstElementChild.id.substr('page'.length)) - 1;

}

}

const url = '/page?ix=' + page_ix;

const resp = await fetch(url);

if (!resp.ok) {

return;

}

const page = await resp.text()

pages.insertAdjacentHTML(position, page);

if (position == 'afterBegin') {

window.scrollTo(0, pages.firstChild.offsetHeight);

}

}

function load_more() {

const autoload = async () => {

if ((window.innerHeight + window.scrollY) >= document.body.parentNode.offsetHeight) {

window.removeEventListener("scroll", autoload);

console.log("loading page to end...");

await load_page('beforeEnd');

window.addEventListener("scroll", autoload);

} else if (window.scrollY == 0) {

window.removeEventListener("scroll", autoload);

console.log("loading page to begin...");

await load_page('afterBegin');

window.addEventListener("scroll", autoload);

}

}

window.addEventListener("scroll", autoload);

}

window.onload = async () => {

const page_ix = (new URLSearchParams(location.search)).get('start') || 0;

await load_page('beforeEnd', page_ix);

await load_page('afterBegin', page_ix - 1);

load_more();

}

`

func page_handler(w http.ResponseWriter, r *http.Request) {

values := r.URL.Query()

page_ix, err := strconv.Atoi(values.Get("ix"))

if err != nil {

http.Error(w, "page number valid faild", http.StatusBadRequest)

return

}

log.Println("page ix:", page_ix)

if page_ix < 0 || page_ix >= doc.NumPage() {

http.Error(w, "load page faild", http.StatusNotFound)

return

}

html, err := doc.HTML(page_ix, false)

if err != nil {

http.Error(w, "load page faild", http.StatusInternalServerError)

return

}

fmt.Fprint(w, html)

}

func index_handler(w http.ResponseWriter, r *http.Request) {

fmt.Fprint(w, frame)

}

func main() {

filename := flag.String("filename", "", "pdf filename")

flag.Parse()

var err error

doc, err = fitz.New(*filename)

if err != nil {

log.Fatal(err)

}

signal_chan := make(chan os.Signal, 1)

signal.Notify(signal_chan, syscall.SIGINT)

go func() {

<-signal_chan

if doc != nil {

doc.Close()

}

os.Exit(0)

}()

http.HandleFunc("/page", page_handler)

http.HandleFunc("/", index_handler)

log.Fatal(http.ListenAndServe(":8000", nil))

}

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OtaHAkKanDTHLOxU2T1v9avA0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券