github.com/andybalholm/giopdf@v0.0.0-20220317170119-aad9a095ad48/pdf/page.go (about)

     1  // Copyright 2014 The Go 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 file.
     4  
     5  package pdf
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  )
    11  
    12  // A Page represent a single page in a PDF file.
    13  // The methods interpret a Page dictionary stored in V.
    14  type Page struct {
    15  	V Value
    16  }
    17  
    18  // Page returns the page for the given page number.
    19  // Page numbers are indexed starting at 1, not 0.
    20  // If the page is not found, Page returns a Page with p.V.IsNull().
    21  func (r *Reader) Page(num int) Page {
    22  	num-- // now 0-indexed
    23  	page := r.Trailer().Key("Root").Key("Pages")
    24  Search:
    25  	for page.Key("Type").Name() == "Pages" {
    26  		count := int(page.Key("Count").Int64())
    27  		if count < num {
    28  			return Page{}
    29  		}
    30  		kids := page.Key("Kids")
    31  		for i := 0; i < kids.Len(); i++ {
    32  			kid := kids.Index(i)
    33  			if kid.Key("Type").Name() == "Pages" {
    34  				c := int(kid.Key("Count").Int64())
    35  				if num < c {
    36  					page = kid
    37  					continue Search
    38  				}
    39  				num -= c
    40  				continue
    41  			}
    42  			if kid.Key("Type").Name() == "Page" {
    43  				if num == 0 {
    44  					return Page{kid}
    45  				}
    46  				num--
    47  			}
    48  		}
    49  		break
    50  	}
    51  	return Page{}
    52  }
    53  
    54  // NumPage returns the number of pages in the PDF file.
    55  func (r *Reader) NumPage() int {
    56  	return int(r.Trailer().Key("Root").Key("Pages").Key("Count").Int64())
    57  }
    58  
    59  func (p Page) findInherited(key string) Value {
    60  	for v := p.V; !v.IsNull(); v = v.Key("Parent") {
    61  		if r := v.Key(key); !r.IsNull() {
    62  			return r
    63  		}
    64  	}
    65  	return Value{}
    66  }
    67  
    68  func (p Page) MediaBox() Value {
    69  	return p.findInherited("MediaBox")
    70  }
    71  
    72  func (p Page) CropBox() Value {
    73  	return p.findInherited("CropBox")
    74  }
    75  
    76  // Resources returns the resources dictionary associated with the page.
    77  func (p Page) Resources() Value {
    78  	return p.findInherited("Resources")
    79  }
    80  
    81  // Fonts returns a list of the fonts associated with the page.
    82  func (p Page) Fonts() []string {
    83  	return p.Resources().Key("Font").Keys()
    84  }
    85  
    86  // Font returns the font with the given name associated with the page.
    87  func (p Page) Font(name string) Font {
    88  	return Font{p.Resources().Key("Font").Key(name)}
    89  }
    90  
    91  // A Font represent a font in a PDF file.
    92  // The methods interpret a Font dictionary stored in V.
    93  type Font struct {
    94  	V Value
    95  }
    96  
    97  // BaseFont returns the font's name (BaseFont property).
    98  func (f Font) BaseFont() string {
    99  	return f.V.Key("BaseFont").Name()
   100  }
   101  
   102  // FirstChar returns the code point of the first character in the font.
   103  func (f Font) FirstChar() int {
   104  	return int(f.V.Key("FirstChar").Int64())
   105  }
   106  
   107  // LastChar returns the code point of the last character in the font.
   108  func (f Font) LastChar() int {
   109  	return int(f.V.Key("LastChar").Int64())
   110  }
   111  
   112  // Widths returns the widths of the glyphs in the font.
   113  // In a well-formed PDF, len(f.Widths()) == f.LastChar()+1 - f.FirstChar().
   114  func (f Font) Widths() []float64 {
   115  	x := f.V.Key("Widths")
   116  	var out []float64
   117  	for i := 0; i < x.Len(); i++ {
   118  		out = append(out, x.Index(i).Float64())
   119  	}
   120  	return out
   121  }
   122  
   123  // Width returns the width of the given code point.
   124  func (f Font) Width(code int) float64 {
   125  	first := f.FirstChar()
   126  	last := f.LastChar()
   127  	if code < first || last < code {
   128  		return 0
   129  	}
   130  	return f.V.Key("Widths").Index(code - first).Float64()
   131  }
   132  
   133  // Encoder returns the encoding between font code point sequences and UTF-8.
   134  func (f Font) Encoder() TextEncoding {
   135  	enc := f.V.Key("Encoding")
   136  	switch enc.Kind() {
   137  	case Name:
   138  		switch enc.Name() {
   139  		case "WinAnsiEncoding":
   140  			return &byteEncoder{&WinAnsiEncoding}
   141  		case "MacRomanEncoding":
   142  			return &byteEncoder{&MacRomanEncoding}
   143  		case "Identity-H":
   144  			// TODO: Should be big-endian UCS-2 decoder
   145  			return &nopEncoder{}
   146  		default:
   147  			println("unknown encoding", enc.Name())
   148  			return &nopEncoder{}
   149  		}
   150  	case Dict:
   151  		return &dictEncoder{enc.Key("Differences")}
   152  	case Null:
   153  		// ok, try ToUnicode
   154  	default:
   155  		println("unexpected encoding", enc.String())
   156  		return &nopEncoder{}
   157  	}
   158  
   159  	toUnicode := f.V.Key("ToUnicode")
   160  	if toUnicode.Kind() == Dict {
   161  		m := readCmap(toUnicode)
   162  		if m == nil {
   163  			return &nopEncoder{}
   164  		}
   165  		return m
   166  	}
   167  
   168  	return &byteEncoder{&pdfDocEncoding}
   169  }
   170  
   171  type dictEncoder struct {
   172  	v Value
   173  }
   174  
   175  func (e *dictEncoder) Decode(raw string) (text string) {
   176  	r := make([]rune, 0, len(raw))
   177  	for i := 0; i < len(raw); i++ {
   178  		ch := rune(raw[i])
   179  		n := -1
   180  		for j := 0; j < e.v.Len(); j++ {
   181  			x := e.v.Index(j)
   182  			if x.Kind() == Integer {
   183  				n = int(x.Int64())
   184  				continue
   185  			}
   186  			if x.Kind() == Name {
   187  				if int(raw[i]) == n {
   188  					r := nameToRune[x.Name()]
   189  					if r != 0 {
   190  						ch = r
   191  						break
   192  					}
   193  				}
   194  				n++
   195  			}
   196  		}
   197  		r = append(r, ch)
   198  	}
   199  	return string(r)
   200  }
   201  
   202  // A TextEncoding represents a mapping between
   203  // font code points and UTF-8 text.
   204  type TextEncoding interface {
   205  	// Decode returns the UTF-8 text corresponding to
   206  	// the sequence of code points in raw.
   207  	Decode(raw string) (text string)
   208  }
   209  
   210  type nopEncoder struct {
   211  }
   212  
   213  func (e *nopEncoder) Decode(raw string) (text string) {
   214  	return raw
   215  }
   216  
   217  type byteEncoder struct {
   218  	table *[256]rune
   219  }
   220  
   221  func (e *byteEncoder) Decode(raw string) (text string) {
   222  	r := make([]rune, 0, len(raw))
   223  	for i := 0; i < len(raw); i++ {
   224  		r = append(r, e.table[raw[i]])
   225  	}
   226  	return string(r)
   227  }
   228  
   229  type cmap struct {
   230  	space   [4][][2]string
   231  	bfrange []bfrange
   232  }
   233  
   234  func (m *cmap) Decode(raw string) (text string) {
   235  	var r []rune
   236  Parse:
   237  	for len(raw) > 0 {
   238  		for n := 1; n <= 4 && n <= len(raw); n++ {
   239  			for _, space := range m.space[n-1] {
   240  				if space[0] <= raw[:n] && raw[:n] <= space[1] {
   241  					text := raw[:n]
   242  					raw = raw[n:]
   243  					for _, bf := range m.bfrange {
   244  						if len(bf.lo) == n && bf.lo <= text && text <= bf.hi {
   245  							if bf.dst.Kind() == String {
   246  								s := bf.dst.RawString()
   247  								if bf.lo != text {
   248  									b := []byte(s)
   249  									b[len(b)-1] += text[len(text)-1] - bf.lo[len(bf.lo)-1]
   250  									s = string(b)
   251  								}
   252  								r = append(r, []rune(utf16Decode(s))...)
   253  								continue Parse
   254  							}
   255  							if bf.dst.Kind() == Array {
   256  								fmt.Printf("array %v\n", bf.dst)
   257  							} else {
   258  								fmt.Printf("unknown dst %v\n", bf.dst)
   259  							}
   260  							r = append(r, noRune)
   261  							continue Parse
   262  						}
   263  					}
   264  					fmt.Printf("no text for %q", text)
   265  					r = append(r, noRune)
   266  					continue Parse
   267  				}
   268  			}
   269  		}
   270  		println("no code space found")
   271  		r = append(r, noRune)
   272  		raw = raw[1:]
   273  	}
   274  	return string(r)
   275  }
   276  
   277  type bfrange struct {
   278  	lo  string
   279  	hi  string
   280  	dst Value
   281  }
   282  
   283  func readCmap(toUnicode Value) *cmap {
   284  	n := -1
   285  	var m cmap
   286  	ok := true
   287  	Interpret(toUnicode, func(stk *Stack, op string) {
   288  		if !ok {
   289  			return
   290  		}
   291  		switch op {
   292  		case "findresource":
   293  			category := stk.Pop()
   294  			key := stk.Pop()
   295  			fmt.Println("findresource", key, category)
   296  			stk.Push(newDict())
   297  		case "begincmap":
   298  			stk.Push(newDict())
   299  		case "endcmap":
   300  			stk.Pop()
   301  		case "begincodespacerange":
   302  			n = int(stk.Pop().Int64())
   303  		case "endcodespacerange":
   304  			if n < 0 {
   305  				println("missing begincodespacerange")
   306  				ok = false
   307  				return
   308  			}
   309  			for i := 0; i < n; i++ {
   310  				hi, lo := stk.Pop().RawString(), stk.Pop().RawString()
   311  				if len(lo) == 0 || len(lo) != len(hi) {
   312  					println("bad codespace range")
   313  					ok = false
   314  					return
   315  				}
   316  				m.space[len(lo)-1] = append(m.space[len(lo)-1], [2]string{lo, hi})
   317  			}
   318  			n = -1
   319  		case "beginbfrange":
   320  			n = int(stk.Pop().Int64())
   321  		case "endbfrange":
   322  			if n < 0 {
   323  				panic("missing beginbfrange")
   324  			}
   325  			for i := 0; i < n; i++ {
   326  				dst, srcHi, srcLo := stk.Pop(), stk.Pop().RawString(), stk.Pop().RawString()
   327  				m.bfrange = append(m.bfrange, bfrange{srcLo, srcHi, dst})
   328  			}
   329  		case "defineresource":
   330  			category := stk.Pop().Name()
   331  			value := stk.Pop()
   332  			key := stk.Pop().Name()
   333  			fmt.Println("defineresource", key, value, category)
   334  			stk.Push(value)
   335  		default:
   336  			println("interp\t", op)
   337  		}
   338  	})
   339  	if !ok {
   340  		return nil
   341  	}
   342  	return &m
   343  }
   344  
   345  type matrix [3][3]float64
   346  
   347  var ident = matrix{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}
   348  
   349  func (x matrix) mul(y matrix) matrix {
   350  	var z matrix
   351  	for i := 0; i < 3; i++ {
   352  		for j := 0; j < 3; j++ {
   353  			for k := 0; k < 3; k++ {
   354  				z[i][j] += x[i][k] * y[k][j]
   355  			}
   356  		}
   357  	}
   358  	return z
   359  }
   360  
   361  // A Text represents a single piece of text drawn on a page.
   362  type Text struct {
   363  	Font     string  // the font used
   364  	FontSize float64 // the font size, in points (1/72 of an inch)
   365  	X        float64 // the X coordinate, in points, increasing left to right
   366  	Y        float64 // the Y coordinate, in points, increasing bottom to top
   367  	W        float64 // the width of the text, in points
   368  	S        string  // the actual UTF-8 text
   369  }
   370  
   371  // A Rect represents a rectangle.
   372  type Rect struct {
   373  	Min, Max Point
   374  }
   375  
   376  // A Point represents an X, Y pair.
   377  type Point struct {
   378  	X float64
   379  	Y float64
   380  }
   381  
   382  // Content describes the basic content on a page: the text and any drawn rectangles.
   383  type Content struct {
   384  	Text []Text
   385  	Rect []Rect
   386  }
   387  
   388  type gstate struct {
   389  	Tc    float64
   390  	Tw    float64
   391  	Th    float64
   392  	Tl    float64
   393  	Tf    Font
   394  	Tfs   float64
   395  	Tmode int
   396  	Trise float64
   397  	Tm    matrix
   398  	Tlm   matrix
   399  	Trm   matrix
   400  	CTM   matrix
   401  }
   402  
   403  // Content returns the page's content.
   404  func (p Page) Content() Content {
   405  	strm := p.V.Key("Contents")
   406  	var enc TextEncoding = &nopEncoder{}
   407  
   408  	var g = gstate{
   409  		Th:  1,
   410  		CTM: ident,
   411  	}
   412  
   413  	var text []Text
   414  	showText := func(s string) {
   415  		n := 0
   416  		for _, ch := range enc.Decode(s) {
   417  			Trm := matrix{{g.Tfs * g.Th, 0, 0}, {0, g.Tfs, 0}, {0, g.Trise, 1}}.mul(g.Tm).mul(g.CTM)
   418  			w0 := g.Tf.Width(int(s[n]))
   419  			n++
   420  			if ch != ' ' {
   421  				f := g.Tf.BaseFont()
   422  				if i := strings.Index(f, "+"); i >= 0 {
   423  					f = f[i+1:]
   424  				}
   425  				text = append(text, Text{f, Trm[0][0], Trm[2][0], Trm[2][1], w0 / 1000 * Trm[0][0], string(ch)})
   426  			}
   427  			tx := w0/1000*g.Tfs + g.Tc
   428  			if ch == ' ' {
   429  				tx += g.Tw
   430  			}
   431  			tx *= g.Th
   432  			g.Tm = matrix{{1, 0, 0}, {0, 1, 0}, {tx, 0, 1}}.mul(g.Tm)
   433  		}
   434  	}
   435  
   436  	var rect []Rect
   437  	var gstack []gstate
   438  	Interpret(strm, func(stk *Stack, op string) {
   439  		n := stk.Len()
   440  		args := make([]Value, n)
   441  		for i := n - 1; i >= 0; i-- {
   442  			args[i] = stk.Pop()
   443  		}
   444  		switch op {
   445  		default:
   446  			//fmt.Println(op, args)
   447  			return
   448  
   449  		case "cm": // update g.CTM
   450  			if len(args) != 6 {
   451  				panic("bad g.Tm")
   452  			}
   453  			var m matrix
   454  			for i := 0; i < 6; i++ {
   455  				m[i/2][i%2] = args[i].Float64()
   456  			}
   457  			m[2][2] = 1
   458  			g.CTM = m.mul(g.CTM)
   459  
   460  		case "gs": // set parameters from graphics state resource
   461  			gs := p.Resources().Key("ExtGState").Key(args[0].Name())
   462  			font := gs.Key("Font")
   463  			if font.Kind() == Array && font.Len() == 2 {
   464  				//fmt.Println("FONT", font)
   465  			}
   466  
   467  		case "f": // fill
   468  		case "g": // setgray
   469  		case "l": // lineto
   470  		case "m": // moveto
   471  
   472  		case "cs": // set colorspace non-stroking
   473  		case "scn": // set color non-stroking
   474  
   475  		case "re": // append rectangle to path
   476  			if len(args) != 4 {
   477  				panic("bad re")
   478  			}
   479  			x, y, w, h := args[0].Float64(), args[1].Float64(), args[2].Float64(), args[3].Float64()
   480  			rect = append(rect, Rect{Point{x, y}, Point{x + w, y + h}})
   481  
   482  		case "q": // save graphics state
   483  			gstack = append(gstack, g)
   484  
   485  		case "Q": // restore graphics state
   486  			n := len(gstack) - 1
   487  			g = gstack[n]
   488  			gstack = gstack[:n]
   489  
   490  		case "BT": // begin text (reset text matrix and line matrix)
   491  			g.Tm = ident
   492  			g.Tlm = g.Tm
   493  
   494  		case "ET": // end text
   495  
   496  		case "T*": // move to start of next line
   497  			x := matrix{{1, 0, 0}, {0, 1, 0}, {0, -g.Tl, 1}}
   498  			g.Tlm = x.mul(g.Tlm)
   499  			g.Tm = g.Tlm
   500  
   501  		case "Tc": // set character spacing
   502  			if len(args) != 1 {
   503  				panic("bad g.Tc")
   504  			}
   505  			g.Tc = args[0].Float64()
   506  
   507  		case "TD": // move text position and set leading
   508  			if len(args) != 2 {
   509  				panic("bad Td")
   510  			}
   511  			g.Tl = -args[1].Float64()
   512  			fallthrough
   513  		case "Td": // move text position
   514  			if len(args) != 2 {
   515  				panic("bad Td")
   516  			}
   517  			tx := args[0].Float64()
   518  			ty := args[1].Float64()
   519  			x := matrix{{1, 0, 0}, {0, 1, 0}, {tx, ty, 1}}
   520  			g.Tlm = x.mul(g.Tlm)
   521  			g.Tm = g.Tlm
   522  
   523  		case "Tf": // set text font and size
   524  			if len(args) != 2 {
   525  				panic("bad TL")
   526  			}
   527  			f := args[0].Name()
   528  			g.Tf = p.Font(f)
   529  			enc = g.Tf.Encoder()
   530  			if enc == nil {
   531  				println("no cmap for", f)
   532  				enc = &nopEncoder{}
   533  			}
   534  			g.Tfs = args[1].Float64()
   535  
   536  		case "\"": // set spacing, move to next line, and show text
   537  			if len(args) != 3 {
   538  				panic("bad \" operator")
   539  			}
   540  			g.Tw = args[0].Float64()
   541  			g.Tc = args[1].Float64()
   542  			args = args[2:]
   543  			fallthrough
   544  		case "'": // move to next line and show text
   545  			if len(args) != 1 {
   546  				panic("bad ' operator")
   547  			}
   548  			x := matrix{{1, 0, 0}, {0, 1, 0}, {0, -g.Tl, 1}}
   549  			g.Tlm = x.mul(g.Tlm)
   550  			g.Tm = g.Tlm
   551  			fallthrough
   552  		case "Tj": // show text
   553  			if len(args) != 1 {
   554  				panic("bad Tj operator")
   555  			}
   556  			showText(args[0].RawString())
   557  
   558  		case "TJ": // show text, allowing individual glyph positioning
   559  			v := args[0]
   560  			for i := 0; i < v.Len(); i++ {
   561  				x := v.Index(i)
   562  				if x.Kind() == String {
   563  					showText(x.RawString())
   564  				} else {
   565  					tx := -x.Float64() / 1000 * g.Tfs * g.Th
   566  					g.Tm = matrix{{1, 0, 0}, {0, 1, 0}, {tx, 0, 1}}.mul(g.Tm)
   567  				}
   568  			}
   569  
   570  		case "TL": // set text leading
   571  			if len(args) != 1 {
   572  				panic("bad TL")
   573  			}
   574  			g.Tl = args[0].Float64()
   575  
   576  		case "Tm": // set text matrix and line matrix
   577  			if len(args) != 6 {
   578  				panic("bad g.Tm")
   579  			}
   580  			var m matrix
   581  			for i := 0; i < 6; i++ {
   582  				m[i/2][i%2] = args[i].Float64()
   583  			}
   584  			m[2][2] = 1
   585  			g.Tm = m
   586  			g.Tlm = m
   587  
   588  		case "Tr": // set text rendering mode
   589  			if len(args) != 1 {
   590  				panic("bad Tr")
   591  			}
   592  			g.Tmode = int(args[0].Int64())
   593  
   594  		case "Ts": // set text rise
   595  			if len(args) != 1 {
   596  				panic("bad Ts")
   597  			}
   598  			g.Trise = args[0].Float64()
   599  
   600  		case "Tw": // set word spacing
   601  			if len(args) != 1 {
   602  				panic("bad g.Tw")
   603  			}
   604  			g.Tw = args[0].Float64()
   605  
   606  		case "Tz": // set horizontal text scaling
   607  			if len(args) != 1 {
   608  				panic("bad Tz")
   609  			}
   610  			g.Th = args[0].Float64() / 100
   611  		}
   612  	})
   613  	return Content{text, rect}
   614  }
   615  
   616  // TextVertical implements sort.Interface for sorting
   617  // a slice of Text values in vertical order, top to bottom,
   618  // and then left to right within a line.
   619  type TextVertical []Text
   620  
   621  func (x TextVertical) Len() int      { return len(x) }
   622  func (x TextVertical) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
   623  func (x TextVertical) Less(i, j int) bool {
   624  	if x[i].Y != x[j].Y {
   625  		return x[i].Y > x[j].Y
   626  	}
   627  	return x[i].X < x[j].X
   628  }
   629  
   630  // TextHorizontal implements sort.Interface for sorting
   631  // a slice of Text values in horizontal order, left to right,
   632  // and then top to bottom within a column.
   633  type TextHorizontal []Text
   634  
   635  func (x TextHorizontal) Len() int      { return len(x) }
   636  func (x TextHorizontal) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
   637  func (x TextHorizontal) Less(i, j int) bool {
   638  	if x[i].X != x[j].X {
   639  		return x[i].X < x[j].X
   640  	}
   641  	return x[i].Y > x[j].Y
   642  }
   643  
   644  // An Outline is a tree describing the outline (also known as the table of contents)
   645  // of a document.
   646  type Outline struct {
   647  	Title string    // title for this element
   648  	Child []Outline // child elements
   649  }
   650  
   651  // Outline returns the document outline.
   652  // The Outline returned is the root of the outline tree and typically has no Title itself.
   653  // That is, the children of the returned root are the top-level entries in the outline.
   654  func (r *Reader) Outline() Outline {
   655  	return buildOutline(r.Trailer().Key("Root").Key("Outlines"))
   656  }
   657  
   658  func buildOutline(entry Value) Outline {
   659  	var x Outline
   660  	x.Title = entry.Key("Title").Text()
   661  	for child := entry.Key("First"); child.Kind() == Dict; child = child.Key("Next") {
   662  		x.Child = append(x.Child, buildOutline(child))
   663  	}
   664  	return x
   665  }