tex.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // The original code in this file comes from
  2. //
  3. // https://git.sr.ht/~sbinet/star-tex/tree/main/item/cmd/dvi-cnv
  4. //
  5. // and is
  6. //
  7. // Copyright ©2021 The star-tex Authors. All rights reserved.
  8. // Use of this source code is governed by a BSD-style
  9. // license that can be found in the LICENSE-STAR-TEX file.
  10. //
  11. // Modifications are
  12. //
  13. // Copyright 2024 The tk9.0-go Authors. All rights reserved.
  14. // Use of this source code is governed by a BSD-style
  15. // license that can be found in the LICENSE file.
  16. package tk9_0 // import "modernc.org/tk9.0"
  17. import (
  18. "bytes"
  19. "fmt"
  20. "image"
  21. "image/color"
  22. "image/draw"
  23. "image/png"
  24. "io"
  25. "strings"
  26. "github.com/disintegration/imaging"
  27. "golang.org/x/image/font"
  28. fixedmath "golang.org/x/image/math/fixed"
  29. "modernc.org/knuth/dvi"
  30. "modernc.org/knuth/font/fixed"
  31. "modernc.org/knuth/font/pkf"
  32. "modernc.org/knuth/kpath"
  33. "modernc.org/knuth/tex"
  34. )
  35. const (
  36. shrink = 1
  37. )
  38. var (
  39. _ dvi.Renderer = (*renderer)(nil)
  40. )
  41. type fntkey struct {
  42. name string
  43. size fixed.Int12_20
  44. }
  45. type renderer struct {
  46. bkg color.Color
  47. bound image.Rectangle
  48. ctx kpath.Context
  49. err error
  50. faces map[fntkey]font.Face
  51. final image.Image
  52. img *image.RGBA
  53. page int
  54. post dvi.CmdPost
  55. pre dvi.CmdPre
  56. scale float64
  57. conv float32 // converts DVI units to pixels
  58. dpi float32
  59. tconv float32 // converts unmagnified DVI units to pixels
  60. bounded bool
  61. }
  62. func newRenderer(ctx kpath.Context, scale float64) *renderer {
  63. return &renderer{ctx: ctx, faces: make(map[fntkey]font.Face), scale: scale}
  64. }
  65. func (pr *renderer) Init(pre *dvi.CmdPre, post *dvi.CmdPost) {
  66. pr.pre = *pre
  67. pr.post = *post
  68. if pr.dpi == 0 {
  69. pr.dpi = 600
  70. }
  71. res := pr.dpi
  72. conv := float32(pr.pre.Num) / 254000.0 * (res / float32(pr.pre.Den))
  73. pr.tconv = conv
  74. pr.conv = conv * float32(pr.pre.Mag) / 1000.0
  75. conv = 1/(float32(pre.Num)/float32(pre.Den)*(float32(pre.Mag)/1000.0)*(pr.dpi*shrink/254000.0)) + 0.5
  76. if pr.bkg == nil {
  77. pr.bkg = color.Transparent
  78. }
  79. }
  80. func (pr *renderer) BOP(bop *dvi.CmdBOP) {
  81. if pr.err != nil {
  82. return
  83. }
  84. pr.page = int(bop.C0)
  85. bnd := image.Rect(0, 0, int(pr.pixels(int32(pr.post.Width))), int(pr.pixels(int32(pr.post.Height))))
  86. pr.img = image.NewRGBA(bnd)
  87. draw.Draw(pr.img, bnd, image.NewUniform(pr.bkg), image.Point{}, draw.Over)
  88. }
  89. func (pr *renderer) DrawGlyph(x, y int32, font dvi.Font, glyph rune, c color.Color) {
  90. if pr.err != nil {
  91. return
  92. }
  93. dot := fixedmath.Point26_6{X: fixedmath.I(int(pr.pixels(x))), Y: fixedmath.I(int(pr.pixels(y)))}
  94. face, ok := pr.face(font)
  95. if !ok {
  96. return
  97. }
  98. dr, mask, maskp, _, ok := face.Glyph(dot, glyph)
  99. if !ok {
  100. pr.setErr(fmt.Errorf("could not find glyph 0x%02x", glyph))
  101. return
  102. }
  103. draw.DrawMask(pr.img, dr, image.NewUniform(c), image.Point{}, mask, maskp, draw.Over)
  104. pr.union(dr)
  105. }
  106. func (pr *renderer) union(r image.Rectangle) {
  107. switch pr.bounded {
  108. case true:
  109. pr.bound = pr.bound.Union(r)
  110. default:
  111. pr.bound = r
  112. pr.bounded = true
  113. }
  114. }
  115. func (pr *renderer) DrawRule(x, y, w, h int32, c color.Color) {
  116. if pr.err != nil {
  117. return
  118. }
  119. r := image.Rect(int(pr.pixels(x)), int(pr.pixels(y)), int(pr.pixels(x+w)), int(pr.pixels(y-h)))
  120. draw.Draw(pr.img, r, image.NewUniform(c), image.Point{}, draw.Over)
  121. pr.union(r)
  122. }
  123. func (pr *renderer) EOP() {
  124. if pr.err != nil {
  125. return
  126. }
  127. pr.final = pr.img.SubImage(pr.bound)
  128. if pr.scale != 1.0 {
  129. pr.final = imaging.Resize(pr.final, int(float64(pr.bound.Max.X-pr.bound.Min.X)*pr.scale+0.5), 0, imaging.Lanczos)
  130. }
  131. }
  132. func (pr *renderer) setErr(err error) {
  133. if pr.err == nil {
  134. pr.err = err
  135. }
  136. }
  137. func (pr *renderer) face(fnt dvi.Font) (font.Face, bool) {
  138. key := fntkey{
  139. name: fnt.Name(),
  140. size: fnt.Size(),
  141. }
  142. if f, ok := pr.faces[key]; ok {
  143. return f, ok
  144. }
  145. fname, err := pr.ctx.Find(key.name + ".pk")
  146. if err != nil {
  147. pr.setErr(fmt.Errorf("could not find font face %q: %+v", key.name, err))
  148. return nil, false
  149. }
  150. f, err := pr.ctx.Open(fname)
  151. if err != nil {
  152. pr.setErr(fmt.Errorf("could not open font face %q: %+v", key.name, err))
  153. return nil, false
  154. }
  155. defer f.Close()
  156. pk, err := pkf.Parse(f)
  157. if err != nil {
  158. pr.setErr(fmt.Errorf("could not parse font face %q: %+v", key.name, err))
  159. return nil, false
  160. }
  161. tfm := fnt.Metrics()
  162. if tfm.Checksum() != pk.Checksum() {
  163. pr.setErr(fmt.Errorf(
  164. "TFM and PK checksum do not match for %q: tfm=0x%x, pk=0x%x",
  165. key.name,
  166. tfm.Checksum(),
  167. pk.Checksum(),
  168. ))
  169. return nil, false
  170. }
  171. face := pkf.NewFace(pk, tfm, &pkf.FaceOptions{
  172. Size: tfm.DesignSize().Float64(),
  173. DPI: float64(pr.dpi),
  174. })
  175. pr.faces[key] = face
  176. return face, true
  177. }
  178. func (pr *renderer) pixels(v int32) int32 {
  179. x := pr.conv * float32(v)
  180. return roundF32(x / shrink)
  181. }
  182. func roundF32(v float32) int32 {
  183. if v > 0 {
  184. return int32(v + 0.5)
  185. }
  186. return int32(v - 0.5)
  187. }
  188. func dvi2png(r io.Reader, scale float64) (_ []byte, err error) {
  189. img, err := dvi2img(r, scale)
  190. if err != nil {
  191. return nil, err
  192. }
  193. var out bytes.Buffer
  194. if err = png.Encode(&out, img); err != nil {
  195. return nil, err
  196. }
  197. return out.Bytes(), nil
  198. }
  199. func dvi2img(r io.Reader, scale float64) (img image.Image, err error) {
  200. ctx := kpath.New()
  201. renderer := newRenderer(ctx, scale)
  202. vm := dvi.NewMachine(
  203. dvi.WithContext(ctx),
  204. dvi.WithRenderer(renderer),
  205. dvi.WithHandlers(dvi.NewColorHandler(ctx)),
  206. dvi.WithOffsetX(0),
  207. dvi.WithOffsetY(0),
  208. )
  209. raw, err := io.ReadAll(r)
  210. if err != nil {
  211. return nil, fmt.Errorf("could not read DVI program file: %w", err)
  212. }
  213. prog, err := dvi.Compile(raw)
  214. if err != nil {
  215. return nil, fmt.Errorf("could not compile DVI program: %w", err)
  216. }
  217. err = vm.Run(prog)
  218. if err != nil {
  219. return nil, fmt.Errorf("could not interpret DVI program: %w", err)
  220. }
  221. if renderer.err != nil {
  222. return nil, fmt.Errorf("could not render DVI program: %w", renderer.err)
  223. }
  224. return renderer.final, nil
  225. }
  226. func tex2dvi(src string) (dvi *bytes.Buffer, err error) {
  227. // To get rid of the page number rendered by default, the function prepends
  228. // "\footline={}\n" to src. Also, "\n\bye\n" is appended to 'src' to make it a
  229. // complete TeX document.
  230. var stdout, stderr, b bytes.Buffer
  231. nm := wmTitle
  232. switch {
  233. case nm == "plain":
  234. nm += "_"
  235. case nm == "":
  236. nm = "x"
  237. }
  238. if err = tex.Main(
  239. strings.NewReader(fmt.Sprintf("\\input plain \\input %s", nm)),
  240. &stdout,
  241. &stderr,
  242. tex.WithInputFile(nm+".tex", strings.NewReader(fmt.Sprintf("\\footline={}\n%s\n\\bye\n", src))),
  243. tex.WithDVIFile(&b),
  244. tex.WithLogFile(io.Discard),
  245. ); err != nil {
  246. a := []string{err.Error()}
  247. if b := stdout.Bytes(); len(b) != 0 {
  248. a = append(a, string(b))
  249. }
  250. if b := stderr.Bytes(); len(b) != 0 {
  251. a = append(a, string(b))
  252. }
  253. return nil, fmt.Errorf("%s", strings.Join(a, "\n"))
  254. }
  255. return &b, nil
  256. }
  257. func sanitizeTeX(s string) (r string) {
  258. s = strings.TrimSpace(s)
  259. a := strings.Fields(s)
  260. return strings.Join(a, " ")
  261. }
  262. // TeX is like Tex2 but report errors using [ErrorMode] and [Error].
  263. func TeX(src string, scale float64) (png []byte) {
  264. b, err := TeX2(src, scale)
  265. if err != nil {
  266. fail(err)
  267. return nil
  268. }
  269. return b
  270. }
  271. // TeX2 renders TeX 'src' as a png file that shows the TeX "snippet" in a fixed
  272. // 600 dpi resolution. The result is afterwards resized using the 'scale'
  273. // factor. Scale factor 1.0 means no resize.
  274. //
  275. // Only plain Tex and a subset of some of the default Computer Modern fonts are
  276. // supported. Many small fonts are not available.
  277. func TeX2(src string, scale float64) (png []byte, err error) {
  278. if src = sanitizeTeX(src); src == "" {
  279. return nil, fmt.Errorf("empty TeX code")
  280. }
  281. dvi, err := tex2dvi(src)
  282. if err != nil {
  283. return nil, err
  284. }
  285. return dvi2png(dvi, scale)
  286. }
  287. // TeXImg renders is line TeX but returns an [image.Image].
  288. func TeXImg(src string, scale float64) (img image.Image) {
  289. img, err := TeXImg2(src, scale)
  290. if err != nil {
  291. fail(err)
  292. return nil
  293. }
  294. return img
  295. }
  296. // TeXImg2 renders is line TeX2 but returns an [image.Image].
  297. func TeXImg2(src string, scale float64) (img image.Image, err error) {
  298. if src = sanitizeTeX(src); src == "" {
  299. return nil, fmt.Errorf("empty TeX code")
  300. }
  301. dvi, err := tex2dvi(src)
  302. if err != nil {
  303. return nil, err
  304. }
  305. return dvi2img(dvi, scale)
  306. }