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 }