converter.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. //Author:TruthHun
  2. //Email:[email protected]
  3. //Date:2018-01-21
  4. package converter
  5. import (
  6. "fmt"
  7. "io/ioutil"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "os/exec"
  13. "errors"
  14. "github.com/lifei6671/mindoc/utils/filetil"
  15. "github.com/lifei6671/mindoc/utils/ziptil"
  16. "github.com/lifei6671/mindoc/utils/cryptil"
  17. "sync"
  18. "html"
  19. )
  20. type Converter struct {
  21. BasePath string
  22. OutputPath string
  23. Config Config
  24. Debug bool
  25. GeneratedCover string
  26. ProcessNum int //并发的任务数量
  27. process chan func()
  28. limitChan chan bool
  29. }
  30. //目录结构
  31. type Toc struct {
  32. Id int `json:"id"`
  33. Link string `json:"link"`
  34. Pid int `json:"pid"`
  35. Title string `json:"title"`
  36. }
  37. //config.json文件解析结构
  38. type Config struct {
  39. Charset string `json:"charset"` //字符编码,默认utf-8编码
  40. Cover string `json:"cover"` //封面图片,或者封面html文件
  41. Timestamp string `json:"date"` //时间日期,如“2018-01-01 12:12:21”,其实是time.Time格式,但是直接用string就好
  42. Description string `json:"description"` //摘要
  43. Footer string `json:"footer"` //pdf的footer
  44. Header string `json:"header"` //pdf的header
  45. Identifier string `json:"identifier"` //即uuid,留空即可
  46. Language string `json:"language"` //语言,如zh、en、zh-CN、en-US等
  47. Creator string `json:"creator"` //作者,即author
  48. Publisher string `json:"publisher"` //出版单位
  49. Contributor string `json:"contributor"` //同Publisher
  50. Title string `json:"title"` //文档标题
  51. Format []string `json:"format"` //导出格式,可选值:pdf、epub、mobi
  52. FontSize string `json:"font_size"` //默认的pdf导出字体大小
  53. PaperSize string `json:"paper_size"` //页面大小
  54. MarginLeft string `json:"margin_left"` //PDF文档左边距,写数字即可,默认72pt
  55. MarginRight string `json:"margin_right"` //PDF文档左边距,写数字即可,默认72pt
  56. MarginTop string `json:"margin_top"` //PDF文档左边距,写数字即可,默认72pt
  57. MarginBottom string `json:"margin_bottom"` //PDF文档左边距,写数字即可,默认72pt
  58. More []string `json:"more"` //更多导出选项[PDF导出选项,具体参考:https://manual.calibre-ebook.com/generated/en/ebook-convert.html#pdf-output-options]
  59. Toc []Toc `json:"toc"` //目录
  60. ///////////////////////////////////////////
  61. Order []string `json:"-"` //这个不需要赋值
  62. }
  63. var (
  64. output = "output" //文档导出文件夹
  65. ebookConvert = "ebook-convert"
  66. )
  67. func CheckConvertCommand() error {
  68. args := []string{ "--version" }
  69. cmd := exec.Command(ebookConvert, args...)
  70. return cmd.Run()
  71. }
  72. // 接口文档 https://manual.calibre-ebook.com/generated/en/ebook-convert.html#table-of-contents
  73. //根据json配置文件,创建文档转化对象
  74. func NewConverter(configFile string, debug ...bool) (converter *Converter, err error) {
  75. var (
  76. cfg Config
  77. basepath string
  78. db bool
  79. )
  80. if len(debug) > 0 {
  81. db = debug[0]
  82. }
  83. if cfg, err = parseConfig(configFile); err == nil {
  84. if basepath, err = filepath.Abs(filepath.Dir(configFile)); err == nil {
  85. //设置默认值
  86. if len(cfg.Timestamp) == 0 {
  87. cfg.Timestamp = time.Now().Format("2006-01-02 15:04:05")
  88. }
  89. if len(cfg.Charset) == 0 {
  90. cfg.Charset = "utf-8"
  91. }
  92. converter = &Converter{
  93. Config: cfg,
  94. BasePath: basepath,
  95. Debug: db,
  96. ProcessNum: 1,
  97. process: make(chan func(),4),
  98. limitChan: make(chan bool,1),
  99. }
  100. }
  101. }
  102. return
  103. }
  104. //执行文档转换
  105. func (convert *Converter) Convert() (err error) {
  106. if !convert.Debug { //调试模式下不删除生成的文件
  107. defer convert.converterDefer() //最后移除创建的多余而文件
  108. }
  109. if convert.process == nil{
  110. convert.process = make(chan func(),4)
  111. }
  112. if convert.limitChan == nil {
  113. if convert.ProcessNum <= 0 {
  114. convert.ProcessNum = 1
  115. }
  116. convert.limitChan = make(chan bool,convert.ProcessNum)
  117. for i := 0; i < convert.ProcessNum;i++{
  118. convert.limitChan <- true
  119. }
  120. }
  121. if err = convert.generateMimeType(); err != nil {
  122. return
  123. }
  124. if err = convert.generateMetaInfo(); err != nil {
  125. return
  126. }
  127. if err = convert.generateTocNcx(); err != nil { //生成目录
  128. return
  129. }
  130. if err = convert.generateSummary(); err != nil { //生成文档内目录
  131. return
  132. }
  133. if err = convert.generateTitlePage(); err != nil { //生成封面
  134. return
  135. }
  136. if err = convert.generateContentOpf(); err != nil { //这个必须是generate*系列方法的最后一个调用
  137. return
  138. }
  139. //将当前文件夹下的所有文件压缩成zip包,然后直接改名成content.epub
  140. f := filepath.Join(convert.OutputPath, "content.epub")
  141. os.Remove(f) //如果原文件存在了,则删除;
  142. if err = ziptil.Zip(convert.BasePath,f); err == nil {
  143. //创建导出文件夹
  144. os.Mkdir(convert.BasePath+"/"+output, os.ModePerm)
  145. if len(convert.Config.Format) > 0 {
  146. var errs []string
  147. go func(convert *Converter) {
  148. for _, v := range convert.Config.Format {
  149. fmt.Println("convert to " + v)
  150. switch strings.ToLower(v) {
  151. case "epub":
  152. convert.process <- func() {
  153. if err = convert.convertToEpub(); err != nil {
  154. errs = append(errs, err.Error())
  155. fmt.Println("转换EPUB文档失败:" + err.Error())
  156. }
  157. }
  158. case "mobi":
  159. convert.process <- func() {
  160. if err = convert.convertToMobi(); err != nil {
  161. errs = append(errs, err.Error())
  162. fmt.Println("转换MOBI文档失败:" + err.Error())
  163. }
  164. }
  165. case "pdf":
  166. convert.process <- func() {
  167. if err = convert.convertToPdf(); err != nil {
  168. fmt.Println("转换PDF文档失败:" + err.Error())
  169. errs = append(errs, err.Error())
  170. }
  171. }
  172. case "docx":
  173. convert.process <- func() {
  174. if err = convert.convertToDocx(); err != nil {
  175. fmt.Println("转换WORD文档失败:" + err.Error())
  176. errs = append(errs, err.Error())
  177. }
  178. }
  179. }
  180. }
  181. close(convert.process)
  182. }(convert)
  183. group := sync.WaitGroup{}
  184. for {
  185. action, isClosed := <-convert.process
  186. if action == nil && !isClosed {
  187. break;
  188. }
  189. group.Add(1)
  190. <- convert.limitChan
  191. go func(group *sync.WaitGroup) {
  192. action()
  193. group.Done()
  194. convert.limitChan <- true
  195. }(&group)
  196. }
  197. group.Wait()
  198. if len(errs) > 0 {
  199. err = errors.New(strings.Join(errs, "\n"))
  200. }
  201. } else {
  202. err = convert.convertToPdf()
  203. if err != nil {
  204. fmt.Println(err)
  205. }
  206. }
  207. } else {
  208. fmt.Println("压缩目录出错" + err.Error())
  209. }
  210. return
  211. }
  212. //删除生成导出文档而创建的文件
  213. func (this *Converter) converterDefer() {
  214. //删除不必要的文件
  215. os.RemoveAll(filepath.Join(this.BasePath, "META-INF"))
  216. os.RemoveAll(filepath.Join(this.BasePath, "content.epub"))
  217. os.RemoveAll(filepath.Join(this.BasePath, "mimetype"))
  218. os.RemoveAll(filepath.Join(this.BasePath, "toc.ncx"))
  219. os.RemoveAll(filepath.Join(this.BasePath, "content.opf"))
  220. os.RemoveAll(filepath.Join(this.BasePath, "titlepage.xhtml")) //封面图片待优化
  221. os.RemoveAll(filepath.Join(this.BasePath, "summary.html")) //文档目录
  222. }
  223. //生成metainfo
  224. func (this *Converter) generateMetaInfo() (err error) {
  225. xml := `<?xml version="1.0"?>
  226. <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
  227. <rootfiles>
  228. <rootfile full-path="content.opf" media-type="application/oebps-package+xml"/>
  229. </rootfiles>
  230. </container>
  231. `
  232. folder := filepath.Join(this.BasePath, "META-INF")
  233. os.MkdirAll(folder, os.ModePerm)
  234. err = ioutil.WriteFile(filepath.Join(folder, "container.xml"), []byte(xml), os.ModePerm)
  235. return
  236. }
  237. //形成mimetyppe
  238. func (this *Converter) generateMimeType() (err error) {
  239. return ioutil.WriteFile(filepath.Join(this.BasePath, "mimetype"), []byte("application/epub+zip"), os.ModePerm)
  240. }
  241. //生成封面
  242. func (this *Converter) generateTitlePage() (err error) {
  243. if ext := strings.ToLower(filepath.Ext(this.Config.Cover)); !(ext == ".html" || ext == ".xhtml") {
  244. xml := `<?xml version='1.0' encoding='` + this.Config.Charset + `'?>
  245. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="` + this.Config.Language + `">
  246. <head>
  247. <meta http-equiv="Content-Type" content="text/html; charset=` + this.Config.Charset + `"/>
  248. <meta name="calibre:cover" content="true"/>
  249. <title>Cover</title>
  250. <style type="text/css" title="override_css">
  251. @page {padding: 0pt; margin:0pt}
  252. body { text-align: center; padding:0pt; margin: 0pt; }
  253. </style>
  254. </head>
  255. <body>
  256. <div>
  257. <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100%" height="100%" viewBox="0 0 800 1068" preserveAspectRatio="none">
  258. <image width="800" height="1068" xlink:href="` + strings.TrimPrefix(this.Config.Cover, "./") + `"/>
  259. </svg>
  260. </div>
  261. </body>
  262. </html>
  263. `
  264. if err = ioutil.WriteFile(filepath.Join(this.BasePath, "titlepage.xhtml"), []byte(xml), os.ModePerm); err == nil {
  265. this.GeneratedCover = "titlepage.xhtml"
  266. }
  267. }
  268. return
  269. }
  270. //生成文档目录
  271. func (this *Converter) generateTocNcx() (err error) {
  272. ncx := `<?xml version='1.0' encoding='` + this.Config.Charset + `'?>
  273. <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="%v">
  274. <head>
  275. <meta content="4" name="dtb:depth"/>
  276. <meta content="calibre (2.85.1)" name="dtb:generator"/>
  277. <meta content="0" name="dtb:totalPageCount"/>
  278. <meta content="0" name="dtb:maxPageNumber"/>
  279. </head>
  280. <docTitle>
  281. <text>%v</text>
  282. </docTitle>
  283. <navMap>%v</navMap>
  284. </ncx>
  285. `
  286. codes, _ := this.tocToXml(0, 1)
  287. ncx = fmt.Sprintf(ncx, this.Config.Language, html.EscapeString(this.Config.Title), strings.Join(codes, ""))
  288. return ioutil.WriteFile(filepath.Join(this.BasePath, "toc.ncx"), []byte(ncx), os.ModePerm)
  289. }
  290. //生成文档目录,即summary.html
  291. func (this *Converter) generateSummary() (err error) {
  292. //目录
  293. summary := `<!DOCTYPE html>
  294. <html lang="` + this.Config.Language + `">
  295. <head>
  296. <meta charset="` + this.Config.Charset + `">
  297. <title>目录</title>
  298. <style>
  299. body{margin: 0px;padding: 0px;}h1{text-align: center;padding: 0px;margin: 0px;}ul,li{list-style: none;}
  300. a{text-decoration: none;color: #4183c4;text-decoration: none;font-size: 16px;line-height: 28px;}
  301. </style>
  302. </head>
  303. <body>
  304. <h1>目&nbsp;&nbsp;&nbsp;&nbsp;录</h1>
  305. %v
  306. </body>
  307. </html>`
  308. summary = fmt.Sprintf(summary, strings.Join(this.tocToSummary(0), ""))
  309. return ioutil.WriteFile(filepath.Join(this.BasePath, "summary.html"), []byte(summary), os.ModePerm)
  310. }
  311. //将toc转成toc.ncx文件
  312. func (this *Converter) tocToXml(pid, idx int) (codes []string, next_idx int) {
  313. var code string
  314. for _, toc := range this.Config.Toc {
  315. if toc.Pid == pid {
  316. code, idx = this.getNavPoint(toc, idx)
  317. codes = append(codes, code)
  318. for _, item := range this.Config.Toc {
  319. if item.Pid == toc.Id {
  320. code, idx = this.getNavPoint(item, idx)
  321. codes = append(codes, code)
  322. var code_arr []string
  323. code_arr, idx = this.tocToXml(item.Id, idx)
  324. codes = append(codes, code_arr...)
  325. codes = append(codes, `</navPoint>`)
  326. }
  327. }
  328. codes = append(codes, `</navPoint>`)
  329. }
  330. }
  331. next_idx = idx
  332. return
  333. }
  334. //将toc转成toc.ncx文件
  335. func (this *Converter) tocToSummary(pid int) (summarys []string) {
  336. summarys = append(summarys, "<ul>")
  337. for _, toc := range this.Config.Toc {
  338. if toc.Pid == pid {
  339. summarys = append(summarys, fmt.Sprintf(`<li><a href="%v">%v</a></li>`, toc.Link, html.EscapeString(toc.Title)))
  340. for _, item := range this.Config.Toc {
  341. if item.Pid == toc.Id {
  342. summarys = append(summarys, fmt.Sprintf(`<li><ul><li><a href="%v">%v</a></li>`, item.Link, html.EscapeString(item.Title)))
  343. summarys = append(summarys, "<li>")
  344. summarys = append(summarys, this.tocToSummary(item.Id)...)
  345. summarys = append(summarys, "</li></ul></li>")
  346. }
  347. }
  348. }
  349. }
  350. summarys = append(summarys, "</ul>")
  351. return
  352. }
  353. //生成navPoint
  354. func (this *Converter) getNavPoint(toc Toc, idx int) (navpoint string, nextidx int) {
  355. navpoint = `
  356. <navPoint id="id%v" playOrder="%v">
  357. <navLabel>
  358. <text>%v</text>
  359. </navLabel>
  360. <content src="%v"/>`
  361. navpoint = fmt.Sprintf(navpoint, toc.Id, idx, html.EscapeString(toc.Title), toc.Link)
  362. this.Config.Order = append(this.Config.Order, toc.Link)
  363. nextidx = idx + 1
  364. return
  365. }
  366. //生成content.opf文件
  367. //倒数第二步调用
  368. func (this *Converter) generateContentOpf() (err error) {
  369. var (
  370. guide string
  371. manifest string
  372. manifestArr []string
  373. spine string //注意:如果存在封面,则需要把封面放在第一个位置
  374. spineArr []string
  375. )
  376. meta := `<dc:title>%v</dc:title>
  377. <dc:contributor opf:role="bkp">%v</dc:contributor>
  378. <dc:publisher>%v</dc:publisher>
  379. <dc:description>%v</dc:description>
  380. <dc:language>%v</dc:language>
  381. <dc:creator opf:file-as="Unknown" opf:role="aut">%v</dc:creator>
  382. <meta name="calibre:timestamp" content="%v"/>
  383. `
  384. meta = fmt.Sprintf(meta, html.EscapeString(this.Config.Title), html.EscapeString(this.Config.Contributor), html.EscapeString(this.Config.Publisher), html.EscapeString(this.Config.Description), this.Config.Language, html.EscapeString(this.Config.Creator), this.Config.Timestamp)
  385. if len(this.Config.Cover) > 0 {
  386. meta = meta + `<meta name="cover" content="cover"/>`
  387. guide = `<reference href="titlepage.xhtml" title="Cover" type="cover"/>`
  388. manifest = fmt.Sprintf(`<item href="%v" id="cover" media-type="%v"/>`, this.Config.Cover, GetMediaType(filepath.Ext(this.Config.Cover)))
  389. spineArr = append(spineArr, `<itemref idref="titlepage"/>`)
  390. }
  391. if _, err := os.Stat(this.BasePath + "/summary.html"); err == nil {
  392. spineArr = append(spineArr, `<itemref idref="summary"/>`) //目录
  393. }
  394. //扫描所有文件
  395. if files, err := filetil.ScanFiles(this.BasePath); err == nil {
  396. basePath := strings.Replace(this.BasePath, "\\", "/", -1)
  397. for _, file := range files {
  398. if !file.IsDir {
  399. ext := strings.ToLower(filepath.Ext(file.Path))
  400. sourcefile := strings.TrimPrefix(file.Path, basePath+"/")
  401. id := "ncx"
  402. if ext != ".ncx" {
  403. if file.Name == "titlepage.xhtml" { //封面
  404. id = "titlepage"
  405. } else if file.Name == "summary.html" { //目录
  406. id = "summary"
  407. } else {
  408. id = cryptil.Md5Crypt(sourcefile)
  409. }
  410. }
  411. if mt := GetMediaType(ext); mt != "" { //不是封面图片,且media-type不为空
  412. if sourcefile != strings.TrimLeft(this.Config.Cover, "./") { //不是封面图片,则追加进来。封面图片前面已经追加进来了
  413. manifestArr = append(manifestArr, fmt.Sprintf(`<item href="%v" id="%v" media-type="%v"/>`, sourcefile, id, mt))
  414. }
  415. }
  416. } else {
  417. fmt.Println(file.Path)
  418. }
  419. }
  420. items := make(map[string]string)
  421. for _, link := range this.Config.Order {
  422. id := cryptil.Md5Crypt(link)
  423. if _, ok := items[id]; !ok { //去重
  424. items[id] = id
  425. spineArr = append(spineArr, fmt.Sprintf(`<itemref idref="%v"/>`, id))
  426. }
  427. }
  428. manifest = manifest + strings.Join(manifestArr, "\n")
  429. spine = strings.Join(spineArr, "\n")
  430. } else {
  431. return err
  432. }
  433. pkg := `<?xml version='1.0' encoding='` + this.Config.Charset + `'?>
  434. <package xmlns="http://www.idpf.org/2007/opf" unique-identifier="uuid_id" version="2.0">
  435. <metadata xmlns:opf="http://www.idpf.org/2007/opf" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">
  436. %v
  437. </metadata>
  438. <manifest>
  439. %v
  440. </manifest>
  441. <spine toc="ncx">
  442. %v
  443. </spine>
  444. %v
  445. </package>
  446. `
  447. if len(guide) > 0 {
  448. guide = `<guide>` + guide + `</guide>`
  449. }
  450. pkg = fmt.Sprintf(pkg, meta, manifest, spine, guide)
  451. return ioutil.WriteFile(filepath.Join(this.BasePath, "content.opf"), []byte(pkg), os.ModePerm)
  452. }
  453. //转成epub
  454. func (this *Converter) convertToEpub() (err error) {
  455. args := []string{
  456. filepath.Join(this.OutputPath, "content.epub"),
  457. filepath.Join(this.OutputPath, output, "book.epub"),
  458. }
  459. //cmd := exec.Command(ebookConvert, args...)
  460. //
  461. //if this.Debug {
  462. // fmt.Println(cmd.Args)
  463. //}
  464. //fmt.Println("正在转换EPUB文件", args[0])
  465. //return cmd.Run()
  466. return filetil.CopyFile(args[0],args[1])
  467. }
  468. //转成mobi
  469. func (this *Converter) convertToMobi() (err error) {
  470. args := []string{
  471. filepath.Join(this.OutputPath, "content.epub"),
  472. filepath.Join(this.OutputPath, output, "book.mobi"),
  473. }
  474. cmd := exec.Command(ebookConvert, args...)
  475. if this.Debug {
  476. fmt.Println(cmd.Args)
  477. }
  478. fmt.Println("正在转换 MOBI 文件", args[0])
  479. return cmd.Run()
  480. }
  481. //转成pdf
  482. func (this *Converter) convertToPdf() (err error) {
  483. args := []string{
  484. filepath.Join(this.OutputPath, "content.epub"),
  485. filepath.Join(this.OutputPath, output, "book.pdf"),
  486. }
  487. //页面大小
  488. if len(this.Config.PaperSize) > 0 {
  489. args = append(args, "--paper-size", this.Config.PaperSize)
  490. }
  491. //文字大小
  492. if len(this.Config.FontSize) > 0 {
  493. args = append(args, "--pdf-default-font-size", this.Config.FontSize)
  494. }
  495. //header template
  496. if len(this.Config.Header) > 0 {
  497. args = append(args, "--pdf-header-template", this.Config.Header)
  498. }
  499. //footer template
  500. if len(this.Config.Footer) > 0 {
  501. args = append(args, "--pdf-footer-template",this.Config.Footer)
  502. }
  503. if strings.Count(this.Config.MarginLeft,"") > 0 {
  504. args = append(args, "--pdf-page-margin-left", this.Config.MarginLeft)
  505. }
  506. if strings.Count(this.Config.MarginTop,"") > 0 {
  507. args = append(args, "--pdf-page-margin-top", this.Config.MarginTop)
  508. }
  509. if strings.Count(this.Config.MarginRight,"") > 0 {
  510. args = append(args, "--pdf-page-margin-right", this.Config.MarginRight)
  511. }
  512. if strings.Count(this.Config.MarginBottom,"") > 0 {
  513. args = append(args, "--pdf-page-margin-bottom", this.Config.MarginBottom)
  514. }
  515. //更多选项
  516. if len(this.Config.More) > 0 {
  517. args = append(args, this.Config.More...)
  518. }
  519. cmd := exec.Command(ebookConvert, args...)
  520. if this.Debug {
  521. fmt.Println(cmd.Args)
  522. }
  523. fmt.Println("正在转换 PDF 文件", args[0])
  524. return cmd.Run()
  525. }
  526. // 转成word
  527. func (this *Converter) convertToDocx() (err error) {
  528. args := []string{
  529. filepath.Join(this.OutputPath , "content.epub"),
  530. filepath.Join(this.OutputPath , output , "book.docx"),
  531. }
  532. args = append(args, "--docx-no-toc")
  533. //页面大小
  534. if len(this.Config.PaperSize) > 0 {
  535. args = append(args, "--docx-page-size", this.Config.PaperSize)
  536. }
  537. if len(this.Config.MarginLeft) > 0 {
  538. args = append(args, "--docx-page-margin-left", this.Config.MarginLeft)
  539. }
  540. if len(this.Config.MarginTop) > 0 {
  541. args = append(args, "--docx-page-margin-top", this.Config.MarginTop)
  542. }
  543. if len(this.Config.MarginRight) > 0 {
  544. args = append(args, "--docx-page-margin-right", this.Config.MarginRight)
  545. }
  546. if len(this.Config.MarginBottom) > 0 {
  547. args = append(args, "--docx-page-margin-bottom", this.Config.MarginBottom)
  548. }
  549. cmd := exec.Command(ebookConvert, args...)
  550. if this.Debug {
  551. fmt.Println(cmd.Args)
  552. }
  553. fmt.Println("正在转换 DOCX 文件", args[0])
  554. return cmd.Run()
  555. }