program.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. // Copyright ©2021 The star-tex Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE-STAR-TEX file.
  4. package dvi
  5. import (
  6. "errors"
  7. "fmt"
  8. "io"
  9. "modernc.org/knuth/internal/iobuf"
  10. )
  11. var (
  12. errNoPre = errors.New("dvi: missing preamble")
  13. errInvalidVersion = errors.New("dvi: invalid DVI version")
  14. errInvalidDVI = errors.New("dvi: invalid DVI file")
  15. errNoBOP = errors.New("dvi: missing BOP")
  16. errNoEOP = errors.New("dvi: missing EOP")
  17. )
  18. type span struct {
  19. beg uint32
  20. end uint32
  21. }
  22. type Program struct {
  23. r *iobuf.Reader
  24. pre CmdPre
  25. post CmdPost
  26. ppost int
  27. npages int
  28. pages []span
  29. fonts map[int]fntdef
  30. max struct {
  31. width int
  32. height int
  33. stack int
  34. }
  35. }
  36. func Compile(instr []byte) (Program, error) {
  37. prog := Program{
  38. r: iobuf.NewReader(instr),
  39. fonts: make(map[int]fntdef),
  40. }
  41. if opCode(prog.r.PeekU8()) != opPre {
  42. return prog, errNoPre
  43. }
  44. prog.pre.read(prog.r)
  45. if prog.pre.Version != dviVersion {
  46. return prog, errInvalidVersion
  47. }
  48. var (
  49. start = prog.r.Pos()
  50. vers uint8
  51. )
  52. // locate post- and post-postamble
  53. _, err := prog.r.Seek(5, io.SeekEnd)
  54. if err != nil {
  55. return prog, fmt.Errorf("dvi: could not find post-postamble: %w", err)
  56. }
  57. for {
  58. v := prog.r.ReadU8()
  59. if v != dviEOF {
  60. vers = v
  61. break
  62. }
  63. _, err = prog.r.Seek(-2, io.SeekCurrent)
  64. if err != nil {
  65. return prog, fmt.Errorf("dvi: could not rewind: %w", err)
  66. }
  67. }
  68. if vers != prog.pre.Version {
  69. return prog, fmt.Errorf(
  70. "dvi: version skew (pre=%d, post=%d)",
  71. prog.pre.Version, vers,
  72. )
  73. }
  74. _, err = prog.r.Seek(-5, io.SeekCurrent)
  75. if err != nil {
  76. return prog, fmt.Errorf("dvi: could not rewind to post-pointer: %w", err)
  77. }
  78. var (
  79. bop = prog.r.ReadU32()
  80. eop = bop
  81. )
  82. prog.ppost = int(bop)
  83. _, err = prog.r.Seek(int64(bop), io.SeekStart)
  84. if err != nil {
  85. return prog, fmt.Errorf("dvi: could not seek to postamble: %w", err)
  86. }
  87. if opCode(prog.r.PeekU8()) != opPost {
  88. return prog, fmt.Errorf("dvi: could not locate postamble: %w", errInvalidDVI)
  89. }
  90. prog.post.read(prog.r)
  91. fonts:
  92. for {
  93. switch op := opCode(prog.r.PeekU8()); op {
  94. case opFntDef1:
  95. cmd := op.cmd().(*CmdFntDef1)
  96. cmd.read(prog.r)
  97. prog.defineFont(int(cmd.ID), fntdef{
  98. ID: int(cmd.ID),
  99. Checksum: cmd.Checksum,
  100. Size: cmd.Size,
  101. Design: cmd.Design,
  102. Area: cmd.Area,
  103. Name: cmd.Font,
  104. })
  105. case opFntDef2:
  106. cmd := op.cmd().(*CmdFntDef2)
  107. cmd.read(prog.r)
  108. prog.defineFont(int(cmd.ID), fntdef{
  109. ID: int(cmd.ID),
  110. Checksum: cmd.Checksum,
  111. Size: cmd.Size,
  112. Design: cmd.Design,
  113. Area: cmd.Area,
  114. Name: cmd.Font,
  115. })
  116. case opFntDef3:
  117. cmd := op.cmd().(*CmdFntDef3)
  118. cmd.read(prog.r)
  119. prog.defineFont(int(cmd.ID), fntdef{
  120. ID: int(cmd.ID),
  121. Checksum: cmd.Checksum,
  122. Size: cmd.Size,
  123. Design: cmd.Design,
  124. Area: cmd.Area,
  125. Name: cmd.Font,
  126. })
  127. case opFntDef4:
  128. cmd := op.cmd().(*CmdFntDef4)
  129. cmd.read(prog.r)
  130. prog.defineFont(int(cmd.ID), fntdef{
  131. ID: int(cmd.ID),
  132. Checksum: cmd.Checksum,
  133. Size: cmd.Size,
  134. Design: cmd.Design,
  135. Area: cmd.Area,
  136. Name: cmd.Font,
  137. })
  138. case opNOP:
  139. cmd := op.cmd()
  140. cmd.read(prog.r)
  141. default:
  142. break fonts
  143. }
  144. }
  145. bop = prog.post.BOP
  146. prog.pages = make([]span, int(prog.post.Pages))
  147. page := len(prog.pages)
  148. // build pages look-up table.
  149. for bop != ^uint32(0) {
  150. page--
  151. prog.pages[page].end = eop
  152. _, err = prog.r.Seek(int64(bop), io.SeekStart)
  153. if err != nil {
  154. return prog, fmt.Errorf("dvi: could not seek to page %d: %w", page, err)
  155. }
  156. var (
  157. pos = uint32(prog.r.Pos())
  158. op = opCode(prog.r.PeekU8())
  159. )
  160. eop = pos
  161. if op != opBOP {
  162. return prog, fmt.Errorf("could not seek to BOP page=%d: op=%v", page, op.cmd().Name())
  163. }
  164. cmd := op.cmd().(*CmdBOP)
  165. cmd.read(prog.r)
  166. prog.pages[int(cmd.C0)-1].beg = pos
  167. bop = uint32(cmd.Prev)
  168. }
  169. prog.r.SetPos(start)
  170. return prog, nil
  171. }
  172. func (prog *Program) defineFont(id int, def fntdef) {
  173. prog.fonts[id] = def
  174. }