我们可以使用 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))
}
领取专属 10元无门槛券
私享最新 技术干货