diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..04343aa8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at support@unidoc.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/README.md b/README.md index 89ed3087..d0cf6782 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,38 @@ -# Version 2 - -The version 2 of UniDoc is currently in alpha. -The pdf functionality has been split into modules. The core subpackage contains core PDF file parsing functionality and primitive objects, whereas the model subpackage provides a higher level interface to the PDF. -The creator package provides a convenient interface for creating image and text based PDF files and reports. - -# Migrating from version 1. - -Migrating is fairly straightforward. A few things are incompatible and will be listed here prior to release. - ---- - # UniDoc -[UniDoc](http://unidoc.io) is a fast and powerful open source library for document manipulation starting off as a PDF -toolkit. This is a library written and supported by the owners -of the [FoxyUtils.com](https://foxyutils.com) website. - -This library is used to power many of the PDF services offered by [FoxyUtils](https://foxyutils.com). The goal is to extend it to -eventually support all of the offered services. +[UniDoc](http://unidoc.io) is a fast and powerful open source PDF library for Go (golang). The library is written and supported by the owners of the [FoxyUtils.com](https://foxyutils.com) website, where the library is used to power many of the PDF services offered. [![wercker status](https://app.wercker.com/status/22b50db125a6d376080f3f0c80d085fa/s/master "wercker status")](https://app.wercker.com/project/bykey/22b50db125a6d376080f3f0c80d085fa) [![GoDoc](https://godoc.org/github.com/unidoc/unidoc?status.svg)](https://godoc.org/github.com/unidoc/unidoc) +# Version 2 + +Version 2.0.0 has been released. Version 2 represents a major improvement over version 1 with capabilities for modifying +and generating PDF contents. The library has been split up into three major packages and a +few smaller ones. The **core** package contains core PDF file parsing functionality and +primitive objects, whereas the **model** subpackage provides a higher level interface to the PDF. +The **creator** package provides a convenient interface for creating image and text based PDF files +and reports. + +See the release announcement: [https://unidoc.io/news/unidoc-v2-released](https://unidoc.io/news/unidoc-v2-released) + + ## Installation ~~~ -go get github.com/unidoc/unidoc +go get github.com/unidoc/unidoc/... ~~~ -## Vendoring -For reliability, we recommend using specific versions and the vendoring capability of golang. -Check out the Releases section to see the tagged releases. - -## Overview - - * Many [features](http://unidoc.io/features) with documented examples. - * Self contained with no external dependencies - * Developer friendly - ## Examples Multiple examples are provided in our example repository. -See the [unidoc-examples](https://github.com/unidoc/unidoc-examples/tree/master) folder. +Many features for processing PDF files with [documented examples](https://unidoc.io/examples) on our website. + Contact us if you need any specific examples. +## Vendoring +For reliability, we recommend using specific versions and the vendoring capability of golang. +Check out the Releases section to see the tagged releases. + ## Copying/License UniDoc is licensed as [AGPL][agpl] software (with extra terms as specified in our license). @@ -60,16 +50,21 @@ These activities include: * creating/manipulating documents for users in a web/server/cloud application * shipping UniDoc with a closed source product -Contact sales for more info: sales@unidoc.io. +Please see [pricing](http://unidoc.io/pricing) to purchase a commercial license or contact sales at sales@unidoc.io for more info. ## Contributing Contributors need to approve the [Contributor License Agreement](https://docs.google.com/a/owlglobal.io/forms/d/1PfTjEAi67-x0JOTU45SDonJnWy1fWB_J1aopGss34bY/viewform) before any code will be reviewed. Preferably add a test case to make sure there is no regression and that the new behaviour is as expected. -## Support +## Support and consulting Please email us at support@unidoc.io for any queries. +Technical support is included with a purchase of a license, as listed on our [pricing](http://unidoc.io/pricing) page. + +If you have any specific tasks that need to be done, we offer consulting in certain cases. +Please contact us with a brief summary of what you need and we will get back to you with a quote, if appropriate. + ## Stay up to date * Follow us on [twitter](https://twitter.com/unidoclib) diff --git a/common/version.go b/common/version.go index ff22d0e4..0382ec22 100644 --- a/common/version.go +++ b/common/version.go @@ -12,11 +12,11 @@ import ( const releaseYear = 2017 const releaseMonth = 7 -const releaseDay = 17 -const releaseHour = 20 -const releaseMin = 45 +const releaseDay = 25 +const releaseHour = 14 +const releaseMin = 37 // Holds version information, when bumping this make sure to bump the released at stamp also. -const Version = "2.0.0-rc.1" +const Version = "2.0.0" var ReleasedAt = time.Date(releaseYear, releaseMonth, releaseDay, releaseHour, releaseMin, 0, 0, time.UTC) diff --git a/doc.go b/doc.go index fc33f187..af7df3d4 100644 --- a/doc.go +++ b/doc.go @@ -3,14 +3,40 @@ * file 'LICENSE.md', which is part of this source code package. */ +// UniDoc is a comprehensive PDF library for Go (golang). The library has advanced capabilities for generating, +// processing and modifying PDFs. UniDoc is written and supported by the owners of the +// FoxyUtils.com website, where the library is used to power many of the PDF services offered. // -// UniDoc is a fast and powerful package for document manipulation starting -// off as a PDF toolkit. This is a commercial open source library written -// and supported by the owners of the [FoxyUtils.com](https://foxyutils.com) -// website. +// Getting More Information // -// Please see the [examples](https://github.com/unidoc/unidoc-examples/tree/master) -// folder. +// Check out the Getting Started and Example sections, which showcase how to install UniDoc and provide numerous +// examples of using UniDoc to generate, process or modify PDF files. +// https://unidoc.io/examples/getting_started/ // - +// The godoc for unidoc provides a detailed breakdown of the API and documentation for packages, types and methods. +// https://godoc.org/github.com/unidoc/unidoc +// +// Overview of Major Packages +// +// The API is composed of a few major packages: +// +// - common: Provides common shared types such as Logger and utilities to check +// license validity. +// +// - pdf/core: The core package defines the primitive PDF object types and handles +// the file reading I/O and parsing the primitive objects. +// +// - pdf/model: The model package builds on the core package, to represent the PDF as +// a structured model of the PDF primitive types. It has a reader and a writer to +// read and process a PDF file based on the structured model. This serves as a basis +// to perform a number of numerous tasks and can be used to work with a PDF in a +// medium to high level interface, although it does require an understanding of the +// PDF format and structure. +// +// - pdf/creator: The PDF creator makes it easy to create new PDFs or modify existing +// PDFs. It can also enable loading a template PDF, adding text/images and +// generating an output PDF. It can be used to add text, images, and generate text +// and graphical reports. It is designed with simplicity in mind, with the goal of +// making it easy to create reports without needing any knowledge about the PDF +// format or specifications. package unidoc diff --git a/pdf/annotator/utils.go b/pdf/annotator/utils.go deleted file mode 100644 index b9c65b71..00000000 --- a/pdf/annotator/utils.go +++ /dev/null @@ -1,3 +0,0 @@ -package annotator - - diff --git a/pdf/contentstream/contentstream.go b/pdf/contentstream/contentstream.go index 47c273dc..f694451f 100644 --- a/pdf/contentstream/contentstream.go +++ b/pdf/contentstream/contentstream.go @@ -54,7 +54,7 @@ func (this *ContentStreamOperations) WrapIfNeeded() *ContentStreamOperations { return this } - *this = append([]*ContentStreamOperation{&ContentStreamOperation{Operand: "q"}}, *this...) + *this = append([]*ContentStreamOperation{{Operand: "q"}}, *this...) depth := 0 for _, op := range *this { diff --git a/pdf/contentstream/encoding.go b/pdf/contentstream/encoding.go index bd2968fc..1239e993 100644 --- a/pdf/contentstream/encoding.go +++ b/pdf/contentstream/encoding.go @@ -64,6 +64,8 @@ func newEncoderFromInlineImage(inlineImage *ContentStreamInlineImage) (StreamEnc return newFlateEncoderFromInlineImage(inlineImage, nil) } else if *filterName == "LZW" { return newLZWEncoderFromInlineImage(inlineImage, nil) + } else if *filterName == "CCF" { + return NewCCITTFaxEncoder(), nil } else { common.Log.Debug("Unsupported inline image encoding filter name : %s", *filterName) return nil, errors.New("Unsupported inline encoding method") diff --git a/pdf/contentstream/inline-image.go b/pdf/contentstream/inline-image.go index cab7e1e4..9e2196c0 100644 --- a/pdf/contentstream/inline-image.go +++ b/pdf/contentstream/inline-image.go @@ -163,11 +163,11 @@ func (this *ContentStreamInlineImage) GetColorSpace(resources *PdfPageResources) return nil, errors.New("Invalid type") } - if *name == "G" { + if *name == "G" || *name == "DeviceGray" { return NewPdfColorspaceDeviceGray(), nil - } else if *name == "RGB" { + } else if *name == "RGB" || *name == "DeviceRGB" { return NewPdfColorspaceDeviceRGB(), nil - } else if *name == "CMYK" { + } else if *name == "CMYK" || *name == "DeviceCMYK" { return NewPdfColorspaceDeviceCMYK(), nil } else if *name == "I" { return nil, errors.New("Unsupported Index colorspace") diff --git a/pdf/contentstream/parser.go b/pdf/contentstream/parser.go index 68ceae9a..ec773521 100644 --- a/pdf/contentstream/parser.go +++ b/pdf/contentstream/parser.go @@ -46,6 +46,7 @@ func (this *ContentStreamParser) Parse() (*ContentStreamOperations, error) { obj, err, isOperand := this.parseObject() if err != nil { if err == io.EOF { + // End of data. Successful exit point. return &operations, nil } return &operations, err @@ -69,9 +70,6 @@ func (this *ContentStreamParser) Parse() (*ContentStreamOperations, error) { operation.Params = append(operation.Params, im) } } - - common.Log.Debug("Operation list: %v\n", operations) - return &operations, nil } // Skip over any spaces. Returns the number of spaces skipped and @@ -189,7 +187,7 @@ func (this *ContentStreamParser) parseName() (PdfObjectName, error) { // A conforming writer shall not use the PostScript syntax for numbers // with non-decimal radices (such as 16#FFFE) or in exponential format // (such as 6.02E23). -// Nontheless, we sometimes get numbers with exponential format, so +// Nonetheless, we sometimes get numbers with exponential format, so // we will support it in the reader (no confusion with other types, so // no compromise). func (this *ContentStreamParser) parseNumber() (PdfObject, error) { @@ -234,6 +232,11 @@ func (this *ContentStreamParser) parseNumber() (PdfObject, error) { if isFloat { fVal, err := strconv.ParseFloat(numStr, 64) + if err != nil { + common.Log.Debug("Error parsing number %q err=%v. Using 0.0. Output may be incorrect", numStr, err) + fVal = 0.0 + err = nil + } o := PdfObjectFloat(fVal) return &o, err } else { diff --git a/pdf/contentstream/processor.go b/pdf/contentstream/processor.go index 550702c7..297cb689 100644 --- a/pdf/contentstream/processor.go +++ b/pdf/contentstream/processor.go @@ -7,7 +7,6 @@ package contentstream import ( "errors" - "fmt" "github.com/unidoc/unidoc/common" . "github.com/unidoc/unidoc/pdf/core" @@ -25,10 +24,6 @@ type GraphicsState struct { type GraphicStateStack []GraphicsState -func (gs *GraphicsState) String() string { - return fmt.Sprintf("%+v", gs) -} - func (gsStack *GraphicStateStack) Push(gs GraphicsState) { *gsStack = append(*gsStack, gs) } @@ -127,7 +122,7 @@ func (csp *ContentStreamProcessor) getColorspace(name string, resources *PdfPage } // Otherwise unsupported. - common.Log.Error("Unknown colorspace requested: %s", name) + common.Log.Debug("Unknown colorspace requested: %s", name) return nil, errors.New("Unsupported colorspace") } @@ -194,7 +189,7 @@ func (csp *ContentStreamProcessor) getInitialColor(cs PdfColorspace) (PdfColor, return nil, nil } - common.Log.Error("Unable to determine initial color for unknown colorspace: %T", cs) + common.Log.Debug("Unable to determine initial color for unknown colorspace: %T", cs) return nil, errors.New("Unsupported colorspace") } @@ -207,6 +202,8 @@ func (this *ContentStreamProcessor) Process(resources *PdfPageResources) error { this.graphicsState.ColorNonStroking = NewPdfColorDeviceGray(0) for _, op := range this.operations { + var err error + // Internal handling. switch op.Operand { case "q": @@ -216,65 +213,34 @@ func (this *ContentStreamProcessor) Process(resources *PdfPageResources) error { // Color operations (Table 74 p. 179) case "CS": - err := this.handleCommand_CS(op, resources) - if err != nil { - return err - } + err = this.handleCommand_CS(op, resources) case "cs": - err := this.handleCommand_cs(op, resources) - if err != nil { - return err - } + err = this.handleCommand_cs(op, resources) case "SC": - err := this.handleCommand_SC(op, resources) - if err != nil { - return err - } + err = this.handleCommand_SC(op, resources) case "SCN": - err := this.handleCommand_SCN(op, resources) - if err != nil { - return err - } + err = this.handleCommand_SCN(op, resources) case "sc": - err := this.handleCommand_sc(op, resources) - if err != nil { - return err - } + err = this.handleCommand_sc(op, resources) case "scn": - err := this.handleCommand_scn(op, resources) - if err != nil { - return err - } + err = this.handleCommand_scn(op, resources) case "G": - err := this.handleCommand_G(op, resources) - if err != nil { - return err - } + err = this.handleCommand_G(op, resources) case "g": - err := this.handleCommand_g(op, resources) - if err != nil { - return err - } + err = this.handleCommand_g(op, resources) case "RG": - err := this.handleCommand_RG(op, resources) - if err != nil { - return err - } + err = this.handleCommand_RG(op, resources) case "rg": - err := this.handleCommand_rg(op, resources) - if err != nil { - return err - } + err = this.handleCommand_rg(op, resources) case "K": - err := this.handleCommand_K(op, resources) - if err != nil { - return err - } + err = this.handleCommand_K(op, resources) case "k": - err := this.handleCommand_k(op, resources) - if err != nil { - return err - } + err = this.handleCommand_k(op, resources) + } + if err != nil { + common.Log.Debug("Processor handling error (%s): %v", op.Operand, err) + common.Log.Debug("Operand: %#v", op.Operand) + return err } // Check if have external handler also, and process if so. @@ -368,8 +334,8 @@ func (this *ContentStreamProcessor) handleCommand_SC(op *ContentStreamOperation, cs := this.graphicsState.ColorspaceStroking if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } @@ -393,8 +359,8 @@ func (this *ContentStreamProcessor) handleCommand_SCN(op *ContentStreamOperation if !isPatternCS(cs) { if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } } @@ -415,8 +381,8 @@ func (this *ContentStreamProcessor) handleCommand_sc(op *ContentStreamOperation, if !isPatternCS(cs) { if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } } @@ -437,14 +403,15 @@ func (this *ContentStreamProcessor) handleCommand_scn(op *ContentStreamOperation if !isPatternCS(cs) { if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } } color, err := cs.ColorFromPdfObjects(op.Params) if err != nil { + common.Log.Debug("ERROR: Fail to get color from params: %+v (CS is %+v)", op.Params, cs) return err } @@ -458,8 +425,8 @@ func (this *ContentStreamProcessor) handleCommand_scn(op *ContentStreamOperation func (this *ContentStreamProcessor) handleCommand_G(op *ContentStreamOperation, resources *PdfPageResources) error { cs := NewPdfColorspaceDeviceGray() if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } @@ -479,8 +446,8 @@ func (this *ContentStreamProcessor) handleCommand_G(op *ContentStreamOperation, func (this *ContentStreamProcessor) handleCommand_g(op *ContentStreamOperation, resources *PdfPageResources) error { cs := NewPdfColorspaceDeviceGray() if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } @@ -500,8 +467,8 @@ func (this *ContentStreamProcessor) handleCommand_g(op *ContentStreamOperation, func (this *ContentStreamProcessor) handleCommand_RG(op *ContentStreamOperation, resources *PdfPageResources) error { cs := NewPdfColorspaceDeviceRGB() if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } @@ -520,8 +487,8 @@ func (this *ContentStreamProcessor) handleCommand_RG(op *ContentStreamOperation, func (this *ContentStreamProcessor) handleCommand_rg(op *ContentStreamOperation, resources *PdfPageResources) error { cs := NewPdfColorspaceDeviceRGB() if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } @@ -541,8 +508,8 @@ func (this *ContentStreamProcessor) handleCommand_rg(op *ContentStreamOperation, func (this *ContentStreamProcessor) handleCommand_K(op *ContentStreamOperation, resources *PdfPageResources) error { cs := NewPdfColorspaceDeviceCMYK() if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } @@ -561,8 +528,8 @@ func (this *ContentStreamProcessor) handleCommand_K(op *ContentStreamOperation, func (this *ContentStreamProcessor) handleCommand_k(op *ContentStreamOperation, resources *PdfPageResources) error { cs := NewPdfColorspaceDeviceCMYK() if len(op.Params) != cs.GetNumComponents() { - common.Log.Error("Invalid number of parameters for SC") - common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs) + common.Log.Debug("Invalid number of parameters for SC") + common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs) return errors.New("Invalid number of parameters") } diff --git a/pdf/core/const.go b/pdf/core/const.go index a1eadec1..6ba8c1e1 100644 --- a/pdf/core/const.go +++ b/pdf/core/const.go @@ -8,5 +8,11 @@ package core import "errors" var ( + // ErrUnsupportedEncodingParameters error indicates that encoding/decoding was attempted with unsupported + // encoding parameters. + // For example when trying to encode with an unsupported Predictor (flate). ErrUnsupportedEncodingParameters = errors.New("Unsupported encoding parameters") + ErrNoCCITTFaxDecode = errors.New("CCITTFaxDecode encoding is not yet implemented") + ErrNoJBIG2Decode = errors.New("JBIG2Decode encoding is not yet implemented") + ErrNoJPXDecode = errors.New("JPXDecode encoding is not yet implemented") ) diff --git a/pdf/core/crossrefs.go b/pdf/core/crossrefs.go index 2bd4b3a6..eea88772 100644 --- a/pdf/core/crossrefs.go +++ b/pdf/core/crossrefs.go @@ -15,15 +15,20 @@ import ( "github.com/unidoc/unidoc/common" ) +// TODO (v3): Create a new type xrefType which can be an integer and can be used for improved type checking. +// TODO (v3): Unexport these constants and rename with camelCase. const ( - XREF_TABLE_ENTRY = iota + // XREF_TABLE_ENTRY indicates a normal xref table entry. + XREF_TABLE_ENTRY = iota + + // XREF_OBJECT_STREAM indicates an xref entry in an xref object stream. XREF_OBJECT_STREAM = iota ) -// Either can be in a normal xref table, or in an xref stream. -// Can point either to a file offset, or to an object stream. -// XrefFileOffset or XrefObjectStream... - +// XrefObject defines a cross reference entry which is a map between object number (with generation number) and the +// location of the actual object, either as a file offset (xref table entry), or as a location within an xref +// stream object (xref object stream). +// TODO (v3): Unexport. type XrefObject struct { xtype int objectNumber int @@ -35,27 +40,38 @@ type XrefObject struct { osObjIndex int } +// XrefTable is a map between object number and corresponding XrefObject. +// TODO (v3): Unexport. +// TODO: Consider changing to a slice, so can maintain the object order without sorting when analyzing. type XrefTable map[int]XrefObject +// ObjectStream represents an object stream's information which can contain multiple indirect objects. +// The information specifies the number of objects and has information about offset locations for +// each object. +// TODO (v3): Unexport. type ObjectStream struct { - N int + N int // TODO (v3): Unexport. ds []byte offsets map[int]int64 } +// ObjectStreams defines a map between object numbers (object streams only) and underlying ObjectStream information. type ObjectStreams map[int]ObjectStream +// ObjectCache defines a map between object numbers and corresponding PdfObject. Serves as a cache for PdfObjects that +// have already been parsed. +// TODO (v3): Unexport. type ObjectCache map[int]PdfObject // Get an object from an object stream. -func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject, error) { +func (parser *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject, error) { var bufReader *bytes.Reader var objstm ObjectStream var cached bool - objstm, cached = this.objstms[sobjNumber] + objstm, cached = parser.objstms[sobjNumber] if !cached { - soi, err := this.LookupByNumber(sobjNumber) + soi, err := parser.LookupByNumber(sobjNumber) if err != nil { common.Log.Debug("Missing object stream with number %d", sobjNumber) return nil, err @@ -66,19 +82,19 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject, return nil, errors.New("Invalid object stream") } - if this.crypter != nil && !this.crypter.isDecrypted(so) { - return nil, errors.New("Need to decrypt the stream !") + if parser.crypter != nil && !parser.crypter.isDecrypted(so) { + return nil, errors.New("Need to decrypt the stream") } sod := so.PdfObjectDictionary common.Log.Trace("so d: %s\n", *sod) name, ok := sod.Get("Type").(*PdfObjectName) if !ok { - common.Log.Error("Object stream should always have a Type") + common.Log.Debug("ERROR: Object stream should always have a Type") return nil, errors.New("Object stream missing Type") } if strings.ToLower(string(*name)) != "objstm" { - common.Log.Error("Object stream type shall always be ObjStm !") + common.Log.Debug("ERROR: Object stream type shall always be ObjStm !") return nil, errors.New("Object stream type != ObjStm") } @@ -101,20 +117,20 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject, // Temporarily change the reader object to this decoded buffer. // Change back afterwards. - bakOffset := this.GetFileOffset() - defer func() { this.SetFileOffset(bakOffset) }() + bakOffset := parser.GetFileOffset() + defer func() { parser.SetFileOffset(bakOffset) }() bufReader = bytes.NewReader(ds) - this.reader = bufio.NewReader(bufReader) + parser.reader = bufio.NewReader(bufReader) common.Log.Trace("Parsing offset map") // Load the offset map (relative to the beginning of the stream...) - var offsets map[int]int64 = make(map[int]int64) + offsets := map[int]int64{} // Object list and offsets. for i := 0; i < int(*N); i++ { - this.skipSpaces() + parser.skipSpaces() // Object number. - obj, err := this.parseNumber() + obj, err := parser.parseNumber() if err != nil { return nil, err } @@ -123,9 +139,9 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject, return nil, errors.New("Invalid object stream offset table") } - this.skipSpaces() + parser.skipSpaces() // Offset. - obj, err = this.parseNumber() + obj, err = parser.parseNumber() if err != nil { return nil, err } @@ -139,30 +155,30 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject, } objstm = ObjectStream{N: int(*N), ds: ds, offsets: offsets} - this.objstms[sobjNumber] = objstm + parser.objstms[sobjNumber] = objstm } else { // Temporarily change the reader object to this decoded buffer. // Point back afterwards. - bakOffset := this.GetFileOffset() - defer func() { this.SetFileOffset(bakOffset) }() + bakOffset := parser.GetFileOffset() + defer func() { parser.SetFileOffset(bakOffset) }() bufReader = bytes.NewReader(objstm.ds) // Temporarily change the reader object to this decoded buffer. - this.reader = bufio.NewReader(bufReader) + parser.reader = bufio.NewReader(bufReader) } offset := objstm.offsets[objNum] common.Log.Trace("ACTUAL offset[%d] = %d", objNum, offset) bufReader.Seek(offset, os.SEEK_SET) - this.reader = bufio.NewReader(bufReader) + parser.reader = bufio.NewReader(bufReader) - bb, _ := this.reader.Peek(100) + bb, _ := parser.reader.Peek(100) common.Log.Trace("OBJ peek \"%s\"", string(bb)) - val, err := this.parseObject() + val, err := parser.parseObject() if err != nil { - common.Log.Error("Fail to read object (%s)", err) + common.Log.Debug("ERROR Fail to read object (%s)", err) return nil, err } if val == nil { @@ -177,27 +193,26 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject, return &io, nil } -// Currently a bit messy.. multiple wrappers. Can we clean up? - -// Outside interface for lookupByNumberWrapper. Default attempts -// repairs of bad xref tables. -func (this *PdfParser) LookupByNumber(objNumber int) (PdfObject, error) { - obj, _, err := this.lookupByNumberWrapper(objNumber, true) +// LookupByNumber looks up a PdfObject by object number. Returns an error on failure. +// TODO (v3): Unexport. +func (parser *PdfParser) LookupByNumber(objNumber int) (PdfObject, error) { + // Outside interface for lookupByNumberWrapper. Default attempts repairs of bad xref tables. + obj, _, err := parser.lookupByNumberWrapper(objNumber, true) return obj, err } // Wrapper for lookupByNumber, checks if object encrypted etc. -func (this *PdfParser) lookupByNumberWrapper(objNumber int, attemptRepairs bool) ( - PdfObject, bool, error) { - obj, inObjStream, err := this.lookupByNumber(objNumber, attemptRepairs) + +func (parser *PdfParser) lookupByNumberWrapper(objNumber int, attemptRepairs bool) (PdfObject, bool, error) { + obj, inObjStream, err := parser.lookupByNumber(objNumber, attemptRepairs) if err != nil { return nil, inObjStream, err } // If encrypted, decrypt it prior to returning. // Do not attempt to decrypt objects within object streams. - if !inObjStream && this.crypter != nil && !this.crypter.isDecrypted(obj) { - err := this.crypter.Decrypt(obj, 0, 0) + if !inObjStream && parser.crypter != nil && !parser.crypter.isDecrypted(obj) { + err := parser.crypter.Decrypt(obj, 0, 0) if err != nil { return nil, inObjStream, err } @@ -206,7 +221,6 @@ func (this *PdfParser) lookupByNumberWrapper(objNumber int, attemptRepairs bool) return obj, inObjStream, nil } -// getObjectNumber returns object number, generation number, error for `obj` func getObjectNumber(obj PdfObject) (int64, int64, error) { if io, isIndirect := obj.(*PdfIndirectObject); isIndirect { return io.ObjectNumber, io.GenerationNumber, nil @@ -219,14 +233,14 @@ func getObjectNumber(obj PdfObject) (int64, int64, error) { // LookupByNumber // Repair signals whether to repair if broken. -func (this *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfObject, bool, error) { - obj, ok := this.ObjCache[objNumber] +func (parser *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfObject, bool, error) { + obj, ok := parser.ObjCache[objNumber] if ok { common.Log.Trace("Returning cached object %d", objNumber) return obj, false, nil } - xref, ok := this.xrefs[objNumber] + xref, ok := parser.xrefs[objNumber] if !ok { // An indirect reference to an undefined object shall not be // considered an error by a conforming reader; it shall be @@ -242,22 +256,22 @@ func (this *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfOb common.Log.Trace("xrefobj gen %d", xref.generation) common.Log.Trace("xrefobj offset %d", xref.offset) - this.rs.Seek(xref.offset, os.SEEK_SET) - this.reader = bufio.NewReader(this.rs) + parser.rs.Seek(xref.offset, os.SEEK_SET) + parser.reader = bufio.NewReader(parser.rs) - obj, err := this.ParseIndirectObject() + obj, err := parser.ParseIndirectObject() if err != nil { - common.Log.Error("Failed reading xref (%s)", err) + common.Log.Debug("ERROR Failed reading xref (%s)", err) // Offset pointing to a non-object. Try to repair the file. if attemptRepairs { common.Log.Debug("Attempting to repair xrefs (top down)") - xrefTable, err := this.repairRebuildXrefsTopDown() + xrefTable, err := parser.repairRebuildXrefsTopDown() if err != nil { - common.Log.Error("Failed repair (%s)", err) + common.Log.Debug("ERROR Failed repair (%s)", err) return nil, false, err } - this.xrefs = *xrefTable - return this.lookupByNumber(objNumber, false) + parser.xrefs = *xrefTable + return parser.lookupByNumber(objNumber, false) } return nil, false, err } @@ -269,19 +283,19 @@ func (this *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfOb realObjNum, _, _ := getObjectNumber(obj) if int(realObjNum) != objNumber { common.Log.Debug("Invalid xrefs: Rebuilding") - err := this.rebuildXrefTable() + err := parser.rebuildXrefTable() if err != nil { return nil, false, err } // Empty the cache. - this.ObjCache = ObjectCache{} + parser.ObjCache = ObjectCache{} // Try looking up again and return. - return this.lookupByNumberWrapper(objNumber, false) + return parser.lookupByNumberWrapper(objNumber, false) } } common.Log.Trace("Returning obj") - this.ObjCache[objNumber] = obj + parser.ObjCache[objNumber] = obj return obj, false, nil } else if xref.xtype == XREF_OBJECT_STREAM { common.Log.Trace("xref from object stream!") @@ -289,22 +303,22 @@ func (this *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfOb common.Log.Trace("Object stream available in object %d/%d", xref.osObjNumber, xref.osObjIndex) if xref.osObjNumber == objNumber { - common.Log.Error("Circular reference!?!") + common.Log.Debug("ERROR Circular reference!?!") return nil, true, errors.New("Xref circular reference") } - _, exists := this.xrefs[xref.osObjNumber] + _, exists := parser.xrefs[xref.osObjNumber] if exists { - optr, err := this.lookupObjectViaOS(xref.osObjNumber, objNumber) //xref.osObjIndex) + optr, err := parser.lookupObjectViaOS(xref.osObjNumber, objNumber) //xref.osObjIndex) if err != nil { - common.Log.Error("Returning ERR (%s)", err) + common.Log.Debug("ERROR Returning ERR (%s)", err) return nil, true, err } common.Log.Trace("= 1 { id0obj, ok := (*idArray)[0].(*PdfObjectString) if !ok { return crypter, errors.New("Invalid trailer ID") @@ -306,10 +311,11 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr return crypter, nil } -func (this *PdfCrypt) GetAccessPermissions() AccessPermissions { +// GetAccessPermissions returns the PDF access permissions as an AccessPermissions object. +func (crypt *PdfCrypt) GetAccessPermissions() AccessPermissions { perms := AccessPermissions{} - P := this.P + P := crypt.P if P&(1<<2) > 0 { perms.Printing = true } @@ -337,6 +343,7 @@ func (this *PdfCrypt) GetAccessPermissions() AccessPermissions { return perms } +// GetP returns the P entry to be used in Encrypt dictionary based on AccessPermissions settings. func (perms AccessPermissions) GetP() int32 { var P int32 = 0 @@ -367,22 +374,21 @@ func (perms AccessPermissions) GetP() int32 { return P } -// Check whether the specified password can be used to decrypt the -// document. -func (this *PdfCrypt) authenticate(password []byte) (bool, error) { +// Check whether the specified password can be used to decrypt the document. +func (crypt *PdfCrypt) authenticate(password []byte) (bool, error) { // Also build the encryption/decryption key. - this.Authenticated = false + crypt.Authenticated = false // Try user password. common.Log.Trace("Debugging authentication - user pass") - authenticated, err := this.Alg6(password) + authenticated, err := crypt.Alg6(password) if err != nil { return false, err } if authenticated { common.Log.Trace("this.Authenticated = True") - this.Authenticated = true + crypt.Authenticated = true return true, nil } @@ -390,13 +396,13 @@ func (this *PdfCrypt) authenticate(password []byte) (bool, error) { // May not be necessary if only want to get all contents. // (user pass needs to be known or empty). common.Log.Trace("Debugging authentication - owner pass") - authenticated, err = this.Alg7(password) + authenticated, err = crypt.Alg7(password) if err != nil { return false, err } if authenticated { common.Log.Trace("this.Authenticated = True") - this.Authenticated = true + crypt.Authenticated = true return true, nil } @@ -409,11 +415,11 @@ func (this *PdfCrypt) authenticate(password []byte) (bool, error) { // The bool flag indicates that the user can access and can view the file. // The AccessPermissions shows what access the user has for editing etc. // An error is returned if there was a problem performing the authentication. -func (this *PdfCrypt) checkAccessRights(password []byte) (bool, AccessPermissions, error) { +func (crypt *PdfCrypt) checkAccessRights(password []byte) (bool, AccessPermissions, error) { perms := AccessPermissions{} // Try owner password -> full rights. - isOwner, err := this.Alg7(password) + isOwner, err := crypt.Alg7(password) if err != nil { return false, perms, err } @@ -431,20 +437,20 @@ func (this *PdfCrypt) checkAccessRights(password []byte) (bool, AccessPermission } // Try user password. - isUser, err := this.Alg6(password) + isUser, err := crypt.Alg6(password) if err != nil { return false, perms, err } if isUser { // User password specified correctly -> access granted with specified permissions. - return true, this.GetAccessPermissions(), nil + return true, crypt.GetAccessPermissions(), nil } // Cannot even view the file. return false, perms, nil } -func (this *PdfCrypt) paddedPass(pass []byte) []byte { +func (crypt *PdfCrypt) paddedPass(pass []byte) []byte { key := make([]byte, 32) if len(pass) >= 32 { for i := 0; i < 32; i++ { @@ -463,10 +469,10 @@ func (this *PdfCrypt) paddedPass(pass []byte) []byte { // Generates a key for encrypting a specific object based on the // object and generation number, as well as the document encryption key. -func (this *PdfCrypt) makeKey(filter string, objNum, genNum uint32, ekey []byte) ([]byte, error) { - cf, ok := this.CryptFilters[filter] +func (crypt *PdfCrypt) makeKey(filter string, objNum, genNum uint32, ekey []byte) ([]byte, error) { + cf, ok := crypt.CryptFilters[filter] if !ok { - common.Log.Error("Unsupported crypt filter (%s)", filter) + common.Log.Debug("ERROR Unsupported crypt filter (%s)", filter) return nil, fmt.Errorf("Unsupported crypt filter (%s)", filter) } isAES := false @@ -503,29 +509,29 @@ func (this *PdfCrypt) makeKey(filter string, objNum, genNum uint32, ekey []byte) if len(ekey)+5 < 16 { return hashb[0 : len(ekey)+5], nil - } else { - return hashb, nil } + + return hashb, nil } // Check if object has already been processed. -func (this *PdfCrypt) isDecrypted(obj PdfObject) bool { - _, ok := this.DecryptedObjects[obj] +func (crypt *PdfCrypt) isDecrypted(obj PdfObject) bool { + _, ok := crypt.DecryptedObjects[obj] if ok { common.Log.Trace("Already decrypted") return true - } else { - common.Log.Trace("Not decrypted yet") - return false } + + common.Log.Trace("Not decrypted yet") + return false } // Decrypt a buffer with a selected crypt filter. -func (this *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) { +func (crypt *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) { common.Log.Trace("Decrypt bytes") - cf, ok := this.CryptFilters[filter] + cf, ok := crypt.CryptFilters[filter] if !ok { - common.Log.Error("Unsupported crypt filter (%s)", filter) + common.Log.Debug("ERROR Unsupported crypt filter (%s)", filter) return nil, fmt.Errorf("Unsupported crypt filter (%s)", filter) } @@ -565,7 +571,7 @@ func (this *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]by // vector is a 16-byte random number that is stored as the first // 16 bytes of the encrypted stream or string. if len(buf) < 16 { - common.Log.Error("AES invalid buf %s", buf) + common.Log.Debug("ERROR AES invalid buf %s", buf) return buf, fmt.Errorf("AES: Buf len < 16 (%d)", len(buf)) } @@ -609,20 +615,20 @@ func (this *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]by // Traverses through all the subobjects (recursive). // // Does not look up references.. That should be done prior to calling. -func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) error { - if this.isDecrypted(obj) { +func (crypt *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) error { + if crypt.isDecrypted(obj) { return nil } if io, isIndirect := obj.(*PdfIndirectObject); isIndirect { - this.DecryptedObjects[io] = true + crypt.DecryptedObjects[io] = true common.Log.Trace("Decrypting indirect %d %d obj!", io.ObjectNumber, io.GenerationNumber) objNum := (*io).ObjectNumber genNum := (*io).GenerationNumber - err := this.Decrypt(io.PdfObject, objNum, genNum) + err := crypt.Decrypt(io.PdfObject, objNum, genNum) if err != nil { return err } @@ -632,7 +638,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e if so, isStream := obj.(*PdfObjectStream); isStream { // Mark as decrypted first to avoid recursive issues. - this.DecryptedObjects[so] = true + crypt.DecryptedObjects[so] = true objNum := (*so).ObjectNumber genNum := (*so).GenerationNumber common.Log.Trace("Decrypting stream %d %d !", objNum, genNum) @@ -643,9 +649,9 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e dict := so.PdfObjectDictionary streamFilter := "Default" // Default RC4. - if this.V >= 4 { - streamFilter = this.StreamFilter - common.Log.Trace("this.StreamFilter = %s", this.StreamFilter) + if crypt.V >= 4 { + streamFilter = crypt.StreamFilter + common.Log.Trace("this.StreamFilter = %s", crypt.StreamFilter) if filters, ok := dict.Get("Filter").(*PdfObjectArray); ok { // Crypt filter can only be the first entry. @@ -658,7 +664,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e // Check if valid crypt filter specified in the decode params. if decodeParams, ok := dict.Get("DecodeParms").(*PdfObjectDictionary); ok { if filterName, ok := decodeParams.Get("Name").(*PdfObjectName); ok { - if _, ok := this.CryptFilters[string(*filterName)]; ok { + if _, ok := crypt.CryptFilters[string(*filterName)]; ok { common.Log.Trace("Using stream filter %s", *filterName) streamFilter = string(*filterName) } @@ -675,17 +681,17 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e } } - err := this.Decrypt(so.PdfObjectDictionary, objNum, genNum) + err := crypt.Decrypt(so.PdfObjectDictionary, objNum, genNum) if err != nil { return err } - okey, err := this.makeKey(streamFilter, uint32(objNum), uint32(genNum), this.EncryptionKey) + okey, err := crypt.makeKey(streamFilter, uint32(objNum), uint32(genNum), crypt.EncryptionKey) if err != nil { return err } - so.Stream, err = this.decryptBytes(so.Stream, streamFilter, okey) + so.Stream, err = crypt.decryptBytes(so.Stream, streamFilter, okey) if err != nil { return err } @@ -698,18 +704,18 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e common.Log.Trace("Decrypting string!") stringFilter := "Default" - if this.V >= 4 { + if crypt.V >= 4 { // Currently only support Identity / RC4. - common.Log.Trace("with %s filter", this.StringFilter) - if this.StringFilter == "Identity" { + common.Log.Trace("with %s filter", crypt.StringFilter) + if crypt.StringFilter == "Identity" { // Identity: pass unchanged: No action. return nil } else { - stringFilter = this.StringFilter + stringFilter = crypt.StringFilter } } - key, err := this.makeKey(stringFilter, uint32(parentObjNum), uint32(parentGenNum), this.EncryptionKey) + key, err := crypt.makeKey(stringFilter, uint32(parentObjNum), uint32(parentGenNum), crypt.EncryptionKey) if err != nil { return err } @@ -720,7 +726,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e decrypted[i] = (*s)[i] } common.Log.Trace("Decrypt string: %s : % x", decrypted, decrypted) - decrypted, err = this.decryptBytes(decrypted, stringFilter, key) + decrypted, err = crypt.decryptBytes(decrypted, stringFilter, key) if err != nil { return err } @@ -731,7 +737,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e if a, isArray := obj.(*PdfObjectArray); isArray { for _, o := range *a { - err := this.Decrypt(o, parentObjNum, parentGenNum) + err := crypt.Decrypt(o, parentObjNum, parentGenNum) if err != nil { return err } @@ -757,7 +763,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e } if string(keyidx) != "Parent" && string(keyidx) != "Prev" && string(keyidx) != "Last" { // Check not needed? - err := this.Decrypt(o, parentObjNum, parentGenNum) + err := crypt.Decrypt(o, parentObjNum, parentGenNum) if err != nil { return err } @@ -770,23 +776,23 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e } // Check if object has already been processed. -func (this *PdfCrypt) isEncrypted(obj PdfObject) bool { - _, ok := this.EncryptedObjects[obj] +func (crypt *PdfCrypt) isEncrypted(obj PdfObject) bool { + _, ok := crypt.EncryptedObjects[obj] if ok { common.Log.Trace("Already encrypted") return true - } else { - common.Log.Trace("Not encrypted yet") - return false } + + common.Log.Trace("Not encrypted yet") + return false } // Encrypt a buffer with the specified crypt filter and key. -func (this *PdfCrypt) encryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) { +func (crypt *PdfCrypt) encryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) { common.Log.Trace("Encrypt bytes") - cf, ok := this.CryptFilters[filter] + cf, ok := crypt.CryptFilters[filter] if !ok { - common.Log.Error("Unsupported crypt filter (%s)", filter) + common.Log.Debug("ERROR Unsupported crypt filter (%s)", filter) return nil, fmt.Errorf("Unsupported crypt filter (%s)", filter) } @@ -857,20 +863,20 @@ func (this *PdfCrypt) encryptBytes(buf []byte, filter string, okey []byte) ([]by // Traverses through all the subobjects (recursive). // // Does not look up references.. That should be done prior to calling. -func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) error { - if this.isEncrypted(obj) { +func (crypt *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) error { + if crypt.isEncrypted(obj) { return nil } if io, isIndirect := obj.(*PdfIndirectObject); isIndirect { - this.EncryptedObjects[io] = true + crypt.EncryptedObjects[io] = true common.Log.Trace("Encrypting indirect %d %d obj!", io.ObjectNumber, io.GenerationNumber) objNum := (*io).ObjectNumber genNum := (*io).GenerationNumber - err := this.Encrypt(io.PdfObject, objNum, genNum) + err := crypt.Encrypt(io.PdfObject, objNum, genNum) if err != nil { return err } @@ -879,7 +885,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e } if so, isStream := obj.(*PdfObjectStream); isStream { - this.EncryptedObjects[so] = true + crypt.EncryptedObjects[so] = true objNum := (*so).ObjectNumber genNum := (*so).GenerationNumber common.Log.Trace("Encrypting stream %d %d !", objNum, genNum) @@ -890,11 +896,11 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e dict := so.PdfObjectDictionary streamFilter := "Default" // Default RC4. - if this.V >= 4 { + if crypt.V >= 4 { // For now. Need to change when we add support for more than // Identity / RC4. - streamFilter = this.StreamFilter - common.Log.Trace("this.StreamFilter = %s", this.StreamFilter) + streamFilter = crypt.StreamFilter + common.Log.Trace("this.StreamFilter = %s", crypt.StreamFilter) if filters, ok := dict.Get("Filter").(*PdfObjectArray); ok { // Crypt filter can only be the first entry. @@ -907,7 +913,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e // Check if valid crypt filter specified in the decode params. if decodeParams, ok := dict.Get("DecodeParms").(*PdfObjectDictionary); ok { if filterName, ok := decodeParams.Get("Name").(*PdfObjectName); ok { - if _, ok := this.CryptFilters[string(*filterName)]; ok { + if _, ok := crypt.CryptFilters[string(*filterName)]; ok { common.Log.Trace("Using stream filter %s", *filterName) streamFilter = string(*filterName) } @@ -924,17 +930,17 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e } } - err := this.Encrypt(so.PdfObjectDictionary, objNum, genNum) + err := crypt.Encrypt(so.PdfObjectDictionary, objNum, genNum) if err != nil { return err } - okey, err := this.makeKey(streamFilter, uint32(objNum), uint32(genNum), this.EncryptionKey) + okey, err := crypt.makeKey(streamFilter, uint32(objNum), uint32(genNum), crypt.EncryptionKey) if err != nil { return err } - so.Stream, err = this.encryptBytes(so.Stream, streamFilter, okey) + so.Stream, err = crypt.encryptBytes(so.Stream, streamFilter, okey) if err != nil { return err } @@ -947,17 +953,17 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e common.Log.Trace("Encrypting string!") stringFilter := "Default" - if this.V >= 4 { - common.Log.Trace("with %s filter", this.StringFilter) - if this.StringFilter == "Identity" { + if crypt.V >= 4 { + common.Log.Trace("with %s filter", crypt.StringFilter) + if crypt.StringFilter == "Identity" { // Identity: pass unchanged: No action. return nil } else { - stringFilter = this.StringFilter + stringFilter = crypt.StringFilter } } - key, err := this.makeKey(stringFilter, uint32(parentObjNum), uint32(parentGenNum), this.EncryptionKey) + key, err := crypt.makeKey(stringFilter, uint32(parentObjNum), uint32(parentGenNum), crypt.EncryptionKey) if err != nil { return err } @@ -967,7 +973,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e encrypted[i] = (*s)[i] } common.Log.Trace("Encrypt string: %s : % x", encrypted, encrypted) - encrypted, err = this.encryptBytes(encrypted, stringFilter, key) + encrypted, err = crypt.encryptBytes(encrypted, stringFilter, key) if err != nil { return err } @@ -978,7 +984,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e if a, isArray := obj.(*PdfObjectArray); isArray { for _, o := range *a { - err := this.Encrypt(o, parentObjNum, parentGenNum) + err := crypt.Encrypt(o, parentObjNum, parentGenNum) if err != nil { return err } @@ -1004,7 +1010,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e continue } if string(keyidx) != "Parent" && string(keyidx) != "Prev" && string(keyidx) != "Last" { // Check not needed? - err := this.Encrypt(o, parentObjNum, parentGenNum) + err := crypt.Encrypt(o, parentObjNum, parentGenNum) if err != nil { return err } @@ -1016,19 +1022,20 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e return nil } -// Algorithm 2: Computing an encryption key. -func (this *PdfCrypt) Alg2(pass []byte) []byte { +// Alg2 computes an encryption key. +// TODO (v3): Unexport. +func (crypt *PdfCrypt) Alg2(pass []byte) []byte { common.Log.Trace("Alg2") - key := this.paddedPass(pass) + key := crypt.paddedPass(pass) h := md5.New() h.Write(key) // Pass O. - h.Write(this.O) + h.Write(crypt.O) // Pass P (Lower order byte first). - var p uint32 = uint32(this.P) + var p uint32 = uint32(crypt.P) var pb = []byte{} for i := 0; i < 4; i++ { pb = append(pb, byte(((p >> uint(8*i)) & 0xff))) @@ -1037,36 +1044,36 @@ func (this *PdfCrypt) Alg2(pass []byte) []byte { common.Log.Trace("go P: % x", pb) // Pass ID[0] from the trailer - h.Write([]byte(this.Id0)) + h.Write([]byte(crypt.Id0)) - common.Log.Trace("this.R = %d encryptMetadata %v", this.R, this.EncryptMetadata) - if (this.R >= 4) && !this.EncryptMetadata { + common.Log.Trace("this.R = %d encryptMetadata %v", crypt.R, crypt.EncryptMetadata) + if (crypt.R >= 4) && !crypt.EncryptMetadata { h.Write([]byte{0xff, 0xff, 0xff, 0xff}) } hashb := h.Sum(nil) - if this.R >= 3 { + if crypt.R >= 3 { for i := 0; i < 50; i++ { h = md5.New() - h.Write(hashb[0 : this.Length/8]) + h.Write(hashb[0 : crypt.Length/8]) hashb = h.Sum(nil) } } - if this.R >= 3 { - return hashb[0 : this.Length/8] + if crypt.R >= 3 { + return hashb[0 : crypt.Length/8] } return hashb[0:5] } // Create the RC4 encryption key. -func (this *PdfCrypt) alg3_key(pass []byte) []byte { +func (crypt *PdfCrypt) alg3Key(pass []byte) []byte { h := md5.New() - okey := this.paddedPass(pass) + okey := crypt.paddedPass(pass) h.Write(okey) - if this.R >= 3 { + if crypt.R >= 3 { for i := 0; i < 50; i++ { hashb := h.Sum(nil) h = md5.New() @@ -1075,25 +1082,25 @@ func (this *PdfCrypt) alg3_key(pass []byte) []byte { } encKey := h.Sum(nil) - if this.R == 2 { + if crypt.R == 2 { encKey = encKey[0:5] } else { - encKey = encKey[0 : this.Length/8] + encKey = encKey[0 : crypt.Length/8] } return encKey } -// Algorithm 3: Computing the encryption dictionary’s O -// (owner password) value. -func (this *PdfCrypt) Alg3(upass, opass []byte) (PdfObjectString, error) { +// Alg3 computes the encryption dictionary’s O (owner password) value. +// TODO (v3): Unexport. +func (crypt *PdfCrypt) Alg3(upass, opass []byte) (PdfObjectString, error) { // Return O string val. O := PdfObjectString("") var encKey []byte if len(opass) > 0 { - encKey = this.alg3_key(opass) + encKey = crypt.alg3Key(opass) } else { - encKey = this.alg3_key(upass) + encKey = crypt.alg3Key(upass) } ociph, err := rc4.NewCipher(encKey) @@ -1101,11 +1108,11 @@ func (this *PdfCrypt) Alg3(upass, opass []byte) (PdfObjectString, error) { return O, errors.New("Failed rc4 ciph") } - ukey := this.paddedPass(upass) + ukey := crypt.paddedPass(upass) encrypted := make([]byte, len(ukey)) ociph.XORKeyStream(encrypted, ukey) - if this.R >= 3 { + if crypt.R >= 3 { encKey2 := make([]byte, len(encKey)) for i := 0; i < 19; i++ { for j := 0; j < len(encKey); j++ { @@ -1123,12 +1130,12 @@ func (this *PdfCrypt) Alg3(upass, opass []byte) (PdfObjectString, error) { return O, nil } -// Algorithm 4: Computing the encryption dictionary’s U (user password) -// value (Security handlers of revision 2). -func (this *PdfCrypt) Alg4(upass []byte) (PdfObjectString, []byte, error) { +// Alg4 computes the encryption dictionary’s U (user password) value (Security handlers of revision 2). +// TODO (v3): Unexport. +func (crypt *PdfCrypt) Alg4(upass []byte) (PdfObjectString, []byte, error) { U := PdfObjectString("") - ekey := this.Alg2(upass) + ekey := crypt.Alg2(upass) ciph, err := rc4.NewCipher(ekey) if err != nil { return U, ekey, errors.New("Failed rc4 ciph") @@ -1142,21 +1149,21 @@ func (this *PdfCrypt) Alg4(upass []byte) (PdfObjectString, []byte, error) { return U, ekey, nil } -// Algorithm 5: Computing the encryption dictionary’s U (user password) -// value (Security handlers of revision 3 or greater). -func (this *PdfCrypt) Alg5(upass []byte) (PdfObjectString, []byte, error) { +// Alg5 computes the encryption dictionary’s U (user password) value (Security handlers of revision 3 or greater). +// TODO (v3): Unexport. +func (crypt *PdfCrypt) Alg5(upass []byte) (PdfObjectString, []byte, error) { U := PdfObjectString("") - ekey := this.Alg2(upass) + ekey := crypt.Alg2(upass) h := md5.New() h.Write([]byte(padding)) - h.Write([]byte(this.Id0)) + h.Write([]byte(crypt.Id0)) hash := h.Sum(nil) common.Log.Trace("Alg5") common.Log.Trace("ekey: % x", ekey) - common.Log.Trace("ID: % x", this.Id0) + common.Log.Trace("ID: % x", crypt.Id0) if len(hash) != 16 { return U, ekey, errors.New("Hash length not 16 bytes") @@ -1206,15 +1213,16 @@ func (this *PdfCrypt) Alg5(upass []byte) (PdfObjectString, []byte, error) { return U, ekey, nil } -// Algorithm 6: Authenticating the user password -func (this *PdfCrypt) Alg6(upass []byte) (bool, error) { +// Alg6 authenticates the user password. +// TODO (v3): Unexport. +func (crypt *PdfCrypt) Alg6(upass []byte) (bool, error) { var uo PdfObjectString var err error var key []byte - if this.R == 2 { - uo, key, err = this.Alg4(upass) - } else if this.R >= 3 { - uo, key, err = this.Alg5(upass) + if crypt.R == 2 { + uo, key, err = crypt.Alg4(upass) + } else if crypt.R >= 3 { + uo, key, err = crypt.Alg5(upass) } else { return false, errors.New("invalid R") } @@ -1223,37 +1231,43 @@ func (this *PdfCrypt) Alg6(upass []byte) (bool, error) { return false, err } - common.Log.Trace("check: % x == % x ?", string(uo), string(this.U)) + common.Log.Trace("check: % x == % x ?", string(uo), string(crypt.U)) - uGen := string(uo) // Generated U from specified pass. - uDoc := string(this.U) // U from the document. - if this.R >= 3 { + uGen := string(uo) // Generated U from specified pass. + uDoc := string(crypt.U) // U from the document. + if crypt.R >= 3 { // comparing on the first 16 bytes in the case of security // handlers of revision 3 or greater), - uGen = uGen[0:16] - uDoc = uDoc[0:16] + if len(uGen) > 16 { + uGen = uGen[0:16] + } + if len(uDoc) > 16 { + uDoc = uDoc[0:16] + } } + if uGen == uDoc { - this.EncryptionKey = key + crypt.EncryptionKey = key return true, nil - } else { - return false, nil } + + return false, nil } -// Algorithm 7: Authenticating the owner password. -func (this *PdfCrypt) Alg7(opass []byte) (bool, error) { - encKey := this.alg3_key(opass) +// Alg7 authenticates the owner password. +// TODO (v3): Unexport. +func (crypt *PdfCrypt) Alg7(opass []byte) (bool, error) { + encKey := crypt.alg3Key(opass) - decrypted := make([]byte, len(this.O)) - if this.R == 2 { + decrypted := make([]byte, len(crypt.O)) + if crypt.R == 2 { ciph, err := rc4.NewCipher(encKey) if err != nil { return false, errors.New("Failed cipher") } - ciph.XORKeyStream(decrypted, this.O) - } else if this.R >= 3 { - s := append([]byte{}, this.O...) + ciph.XORKeyStream(decrypted, crypt.O) + } else if crypt.R >= 3 { + s := append([]byte{}, crypt.O...) for i := 0; i < 20; i++ { //newKey := encKey newKey := append([]byte{}, encKey...) @@ -1271,7 +1285,7 @@ func (this *PdfCrypt) Alg7(opass []byte) (bool, error) { return false, errors.New("invalid R") } - auth, err := this.Alg6(decrypted) + auth, err := crypt.Alg6(decrypted) if err != nil { return false, nil } diff --git a/pdf/core/crypt_test.go b/pdf/core/crypt_test.go index 58d3743f..b85615e1 100644 --- a/pdf/core/crypt_test.go +++ b/pdf/core/crypt_test.go @@ -179,7 +179,7 @@ func TestDecryption1(t *testing.T) { parser := PdfParser{} parser.xrefs = make(XrefTable) parser.objstms = make(ObjectStreams) - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) parser.crypter = &crypter obj, err := parser.ParseIndirectObject() diff --git a/pdf/core/doc.go b/pdf/core/doc.go new file mode 100644 index 00000000..09a1e1b1 --- /dev/null +++ b/pdf/core/doc.go @@ -0,0 +1,9 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'LICENSE.md', which is part of this source code package. + */ + +// Package core defines and implements the primitive PDF object types in golang, and provides functionality +// for parsing those from a PDF file stream. This includes I/O handling, cross references, repairs, encryption, +// encoding and other core capabilities. +package core diff --git a/pdf/core/encoding.go b/pdf/core/encoding.go index eb545938..8d7a22ee 100644 --- a/pdf/core/encoding.go +++ b/pdf/core/encoding.go @@ -10,8 +10,12 @@ package core // - FlateDecode // - LZW // - DCT Decode (JPEG) +// - RunLength // - ASCII Hex // - ASCII85 +// - CCITT Fax (dummy) +// - JBIG2 (dummy) +// - JPX (dummy) import ( "bytes" @@ -33,12 +37,16 @@ import ( ) const ( - StreamEncodingFilterNameFlate = "FlateDecode" - StreamEncodingFilterNameLZW = "LZWDecode" - StreamEncodingFilterNameDCT = "DCTDecode" - StreamEncodingFilterNameASCIIHex = "ASCIIHexDecode" - StreamEncodingFilterNameASCII85 = "ASCII85Decode" - StreamEncodingFilterNameRaw = "Raw" + StreamEncodingFilterNameFlate = "FlateDecode" + StreamEncodingFilterNameLZW = "LZWDecode" + StreamEncodingFilterNameDCT = "DCTDecode" + StreamEncodingFilterNameRunLength = "RunLengthDecode" + StreamEncodingFilterNameASCIIHex = "ASCIIHexDecode" + StreamEncodingFilterNameASCII85 = "ASCII85Decode" + StreamEncodingFilterNameCCITTFax = "CCITTFaxDecode" + StreamEncodingFilterNameJBIG2 = "JBIG2Decode" + StreamEncodingFilterNameJPX = "JPXDecode" + StreamEncodingFilterNameRaw = "Raw" ) const ( @@ -141,8 +149,15 @@ func newFlateEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObje // If decodeParams not provided, see if we can get from the stream. if decodeParams == nil { - obj := encDict.Get("DecodeParms") + obj := TraceToDirectObject(encDict.Get("DecodeParms")) if obj != nil { + if arr, isArr := obj.(*PdfObjectArray); isArr { + if len(*arr) != 1 { + common.Log.Debug("Error: DecodeParms array length != 1 (%d)", len(*arr)) + return nil, errors.New("Range check error") + } + obj = TraceToDirectObject((*arr)[0]) + } dp, isDict := obj.(*PdfObjectDictionary) if !isDict { common.Log.Debug("Error: DecodeParms not a dictionary (%T)", obj) @@ -237,7 +252,7 @@ func (this *FlateEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, erro common.Log.Trace("FlateDecode stream") common.Log.Trace("Predictor: %d", this.Predictor) if this.BitsPerComponent != 8 { - return nil, fmt.Errorf("Invalid BitsPerComponent (only 8 supported)") + return nil, fmt.Errorf("Invalid BitsPerComponent=%d (only 8 supported)", this.BitsPerComponent) } outData, err := this.DecodeBytes(streamObj.Stream) @@ -253,18 +268,21 @@ func (this *FlateEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, erro common.Log.Trace("Colors: %d", this.Colors) rowLength := int(this.Columns) * this.Colors + if rowLength < 1 { + // No data. Return empty set. + return []byte{}, nil + } rows := len(outData) / rowLength if len(outData)%rowLength != 0 { - common.Log.Error("TIFF encoding: Invalid row length (%d/%d)", - len(outData), rowLength) + common.Log.Debug("ERROR: TIFF encoding: Invalid row length...") return nil, fmt.Errorf("Invalid row length (%d/%d)", len(outData), rowLength) } - if rowLength%this.Colors != 0 { - common.Log.Error("Invalid row length (%d) for colors %d", - rowLength, this.Colors) - return nil, fmt.Errorf("Invalid row length (%d) for colors %d", - rowLength, this.Colors) + return nil, fmt.Errorf("Invalid row length (%d) for colors %d", rowLength, this.Colors) + } + if rowLength > len(outData) { + common.Log.Debug("Row length cannot be longer than data length (%d/%d)", rowLength, len(outData)) + return nil, errors.New("Range check error") } common.Log.Trace("inp outData (%d): % x", len(outData), outData) @@ -290,9 +308,12 @@ func (this *FlateEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, erro rowLength := int(this.Columns*this.Colors + 1) // 1 byte to specify predictor algorithms per row. rows := len(outData) / rowLength if len(outData)%rowLength != 0 { - common.Log.Error("Invalid row length (%d/%d)", len(outData), rowLength) return nil, fmt.Errorf("Invalid row length (%d/%d)", len(outData), rowLength) } + if rowLength > len(outData) { + common.Log.Debug("Row length cannot be longer than data length (%d/%d)", rowLength, len(outData)) + return nil, errors.New("Range check error") + } pOutBuffer := bytes.NewBuffer(nil) @@ -508,12 +529,19 @@ func newLZWEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObject if decodeParams == nil { obj := encDict.Get("DecodeParms") if obj != nil { - dp, isDict := obj.(*PdfObjectDictionary) - if !isDict { - common.Log.Debug("Error: DecodeParms not a dictionary (%T)", obj) + if dp, isDict := obj.(*PdfObjectDictionary); isDict { + decodeParams = dp + } else if a, isArr := obj.(*PdfObjectArray); isArr { + if len(*a) == 1 { + if dp, isDict := (*a)[0].(*PdfObjectDictionary); isDict { + decodeParams = dp + } + } + } + if decodeParams == nil { + common.Log.Error("DecodeParms not a dictionary %#v", obj) return nil, fmt.Errorf("Invalid DecodeParms") } - decodeParams = dp } } @@ -638,15 +666,25 @@ func (this *LZWEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) common.Log.Trace("Tiff encoding") rowLength := int(this.Columns) * this.Colors + if rowLength < 1 { + // No data. Return empty set. + return []byte{}, nil + } + rows := len(outData) / rowLength if len(outData)%rowLength != 0 { - common.Log.Error("TIFF encoding: Invalid row length...") + common.Log.Debug("ERROR: TIFF encoding: Invalid row length...") return nil, fmt.Errorf("Invalid row length (%d/%d)", len(outData), rowLength) } if rowLength%this.Colors != 0 { return nil, fmt.Errorf("Invalid row length (%d) for colors %d", rowLength, this.Colors) } + + if rowLength > len(outData) { + common.Log.Debug("Row length cannot be longer than data length (%d/%d)", rowLength, len(outData)) + return nil, errors.New("Range check error") + } common.Log.Trace("inp outData (%d): % x", len(outData), outData) pOutBuffer := bytes.NewBuffer(nil) @@ -671,11 +709,18 @@ func (this *LZWEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) // Columns represents the number of samples per row; Each sample can contain multiple color // components. rowLength := int(this.Columns*this.Colors + 1) // 1 byte to specify predictor algorithms per row. + if rowLength < 1 { + // No data. Return empty set. + return []byte{}, nil + } rows := len(outData) / rowLength if len(outData)%rowLength != 0 { - common.Log.Error("Invalid row length (%d/%d)", len(outData), rowLength) return nil, fmt.Errorf("Invalid row length (%d/%d)", len(outData), rowLength) } + if rowLength > len(outData) { + common.Log.Debug("Row length cannot be longer than data length (%d/%d)", rowLength, len(outData)) + return nil, errors.New("Range check error") + } pOutBuffer := bytes.NewBuffer(nil) @@ -1055,6 +1100,148 @@ func (this *DCTEncoder) EncodeBytes(data []byte) ([]byte, error) { return buf.Bytes(), nil } +// Run length encoding. +type RunLengthEncoder struct { +} + +// Make a new run length encoder +func NewRunLengthEncoder() *RunLengthEncoder { + return &RunLengthEncoder{} +} + +func (this *RunLengthEncoder) GetFilterName() string { + return StreamEncodingFilterNameRunLength +} + +// Create a new run length decoder from a stream object. +func newRunLengthEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObjectDictionary) (*RunLengthEncoder, error) { + return NewRunLengthEncoder(), nil +} + +/* + 7.4.5 RunLengthDecode Filter + The RunLengthDecode filter decodes data that has been encoded in a simple byte-oriented format based on run length. + The encoded data shall be a sequence of runs, where each run shall consist of a length byte followed by 1 to 128 + bytes of data. If the length byte is in the range 0 to 127, the following length + 1 (1 to 128) bytes shall be + copied literally during decompression. If length is in the range 129 to 255, the following single byte shall be + copied 257 - length (2 to 128) times during decompression. A length value of 128 shall denote EOD. +*/ +func (this *RunLengthEncoder) DecodeBytes(encoded []byte) ([]byte, error) { + bufReader := bytes.NewReader(encoded) + inb := []byte{} + for { + b, err := bufReader.ReadByte() + if err != nil { + return nil, err + } + if b > 128 { + v, err := bufReader.ReadByte() + if err != nil { + return nil, err + } + for i := 0; i < 257-int(b); i++ { + inb = append(inb, v) + } + } else if b < 128 { + for i := 0; i < int(b)+1; i++ { + v, err := bufReader.ReadByte() + if err != nil { + return nil, err + } + inb = append(inb, v) + } + } else { + break + } + } + + return inb, nil +} + +// Decode RunLengthEncoded stream object and give back decoded bytes. +func (this *RunLengthEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) { + return this.DecodeBytes(streamObj.Stream) +} + +// Encode a bytes array and return the encoded value based on the encoder parameters. +func (this *RunLengthEncoder) EncodeBytes(data []byte) ([]byte, error) { + bufReader := bytes.NewReader(data) + inb := []byte{} + literal := []byte{} + + b0, err := bufReader.ReadByte() + if err == io.EOF { + return []byte{}, nil + } else if err != nil { + return nil, err + } + runLen := 1 + + for { + b, err := bufReader.ReadByte() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + + if b == b0 { + if len(literal) > 0 { + literal = literal[:len(literal)-1] + if len(literal) > 0 { + inb = append(inb, byte(len(literal)-1)) + inb = append(inb, literal...) + } + runLen = 1 + literal = []byte{} + } + runLen++ + if runLen >= 127 { + inb = append(inb, byte(257-runLen), b0) + runLen = 0 + } + + } else { + if runLen > 0 { + if runLen == 1 { + literal = []byte{b0} + } else { + inb = append(inb, byte(257-runLen), b0) + } + + runLen = 0 + } + literal = append(literal, b) + if len(literal) >= 127 { + inb = append(inb, byte(len(literal)-1)) + inb = append(inb, literal...) + literal = []byte{} + } + } + b0 = b + } + + if len(literal) > 0 { + inb = append(inb, byte(len(literal)-1)) + inb = append(inb, literal...) + } else if runLen > 0 { + inb = append(inb, byte(257-runLen), b0) + } + inb = append(inb, 128) + return inb, nil +} + +func (this *RunLengthEncoder) MakeDecodeParams() PdfObject { + return nil +} + +// Make a new instance of an encoding dictionary for a stream object. +func (this *RunLengthEncoder) MakeStreamDict() *PdfObjectDictionary { + dict := MakeDict() + dict.Set("Filter", MakeName(this.GetFilterName())) + return dict +} + ///// // ASCII hex encoder/decoder. type ASCIIHexEncoder struct { @@ -1336,6 +1523,117 @@ func (this *RawEncoder) EncodeBytes(data []byte) ([]byte, error) { return data, nil } +// +// CCITTFax encoder/decoder (dummy, for now) +// +type CCITTFaxEncoder struct{} + +func NewCCITTFaxEncoder() *CCITTFaxEncoder { + return &CCITTFaxEncoder{} +} + +func (this *CCITTFaxEncoder) GetFilterName() string { + return StreamEncodingFilterNameCCITTFax +} + +func (this *CCITTFaxEncoder) MakeDecodeParams() PdfObject { + return nil +} + +// Make a new instance of an encoding dictionary for a stream object. +func (this *CCITTFaxEncoder) MakeStreamDict() *PdfObjectDictionary { + return MakeDict() +} + +func (this *CCITTFaxEncoder) DecodeBytes(encoded []byte) ([]byte, error) { + common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName()) + return encoded, ErrNoCCITTFaxDecode +} + +func (this *CCITTFaxEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) { + common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName()) + return streamObj.Stream, ErrNoCCITTFaxDecode +} + +func (this *CCITTFaxEncoder) EncodeBytes(data []byte) ([]byte, error) { + common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName()) + return data, ErrNoCCITTFaxDecode +} + +// +// JBIG2 encoder/decoder (dummy, for now) +// +type JBIG2Encoder struct{} + +func NewJBIG2Encoder() *JBIG2Encoder { + return &JBIG2Encoder{} +} + +func (this *JBIG2Encoder) GetFilterName() string { + return StreamEncodingFilterNameJBIG2 +} + +func (this *JBIG2Encoder) MakeDecodeParams() PdfObject { + return nil +} + +// Make a new instance of an encoding dictionary for a stream object. +func (this *JBIG2Encoder) MakeStreamDict() *PdfObjectDictionary { + return MakeDict() +} + +func (this *JBIG2Encoder) DecodeBytes(encoded []byte) ([]byte, error) { + common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName()) + return encoded, ErrNoJBIG2Decode +} + +func (this *JBIG2Encoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) { + common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName()) + return streamObj.Stream, ErrNoJBIG2Decode +} + +func (this *JBIG2Encoder) EncodeBytes(data []byte) ([]byte, error) { + common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName()) + return data, ErrNoJBIG2Decode +} + +// +// JPX encoder/decoder (dummy, for now) +// +type JPXEncoder struct{} + +func NewJPXEncoder() *JPXEncoder { + return &JPXEncoder{} +} + +func (this *JPXEncoder) GetFilterName() string { + return StreamEncodingFilterNameJPX +} + +func (this *JPXEncoder) MakeDecodeParams() PdfObject { + return nil +} + +// Make a new instance of an encoding dictionary for a stream object. +func (this *JPXEncoder) MakeStreamDict() *PdfObjectDictionary { + return MakeDict() +} + +func (this *JPXEncoder) DecodeBytes(encoded []byte) ([]byte, error) { + common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName()) + return encoded, ErrNoJPXDecode +} + +func (this *JPXEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) { + common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName()) + return streamObj.Stream, ErrNoJPXDecode +} + +func (this *JPXEncoder) EncodeBytes(data []byte) ([]byte, error) { + common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName()) + return data, ErrNoJPXDecode +} + // // Multi encoder: support serial encoding. // @@ -1376,10 +1674,11 @@ func newMultiEncoderFromStream(streamObj *PdfObjectStream) (*MultiEncoder, error arr, isArray := obj.(*PdfObjectArray) if isArray { for _, dictObj := range *arr { + dictObj = TraceToDirectObject(dictObj) if dict, is := dictObj.(*PdfObjectDictionary); is { decodeParamsArray = append(decodeParamsArray, dict) } else { - decodeParamsArray = append(decodeParamsArray, nil) + decodeParamsArray = append(decodeParamsArray, MakeDict()) } } } @@ -1422,7 +1721,7 @@ func newMultiEncoderFromStream(streamObj *PdfObjectStream) (*MultiEncoder, error dParams = dict } - common.Log.Trace("Next name: %s", *name) + common.Log.Trace("Next name: %s, dp: %v, dParams: %v", *name, dp, dParams) if *name == StreamEncodingFilterNameFlate { // XXX: need to separate out the DecodeParms.. encoder, err := newFlateEncoderFromStream(streamObj, dParams) diff --git a/pdf/core/encoding_test.go b/pdf/core/encoding_test.go index 528f01b6..6af024ee 100644 --- a/pdf/core/encoding_test.go +++ b/pdf/core/encoding_test.go @@ -71,6 +71,28 @@ func TestLZWEncoding(t *testing.T) { } } +// Test run length encoding. +func TestRunLengthEncoding(t *testing.T) { + rawStream := []byte("this is a dummy text with some \x01\x02\x03 binary data") + encoder := NewRunLengthEncoder() + encoded, err := encoder.EncodeBytes(rawStream) + if err != nil { + t.Errorf("Failed to RunLength encode data: %v", err) + return + } + decoded, err := encoder.DecodeBytes(encoded) + if err != nil { + t.Errorf("Failed to RunLength decode data: %v", err) + return + } + if !compareSlices(decoded, rawStream) { + t.Errorf("Slices not matching. RunLength") + t.Errorf("Decoded (%d): % x", len(encoded), encoded) + t.Errorf("Raw (%d): % x", len(rawStream), rawStream) + return + } +} + // Test ASCII hex encoding. func TestASCIIHexEncoding(t *testing.T) { byteData := []byte{0xDE, 0xAD, 0xBE, 0xEF} diff --git a/pdf/core/fuzz_test.go b/pdf/core/fuzz_test.go index fd64fd86..6188c227 100644 --- a/pdf/core/fuzz_test.go +++ b/pdf/core/fuzz_test.go @@ -17,7 +17,7 @@ func init() { // when passing a reference to a non-existing object. func TestFuzzParserTrace1(t *testing.T) { parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(" /Name") + parser.rs, parser.reader, parser.fileSize = makeReaderForText(" /Name") ref := &PdfObjectReference{ObjectNumber: -1} obj, err := parser.Trace(ref) @@ -53,7 +53,7 @@ endstream parser := PdfParser{} parser.xrefs = make(XrefTable) parser.objstms = make(ObjectStreams) - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) parser.streamLengthReferenceLookupInProgress = map[int64]bool{} // Point to the start of the stream (where obj 13 starts). @@ -86,7 +86,7 @@ endstream parser := PdfParser{} parser.xrefs = make(XrefTable) parser.objstms = make(ObjectStreams) - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) parser.streamLengthReferenceLookupInProgress = map[int64]bool{} // Point to the start of the stream (where obj 13 starts). @@ -109,7 +109,7 @@ endstream // Test for problem where Encrypt pointing a reference to a non-existing object. func TestFuzzIsEncryptedFail1(t *testing.T) { parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(" /Name") + parser.rs, parser.reader, parser.fileSize = makeReaderForText(" /Name") ref := &PdfObjectReference{ObjectNumber: -1} @@ -126,7 +126,7 @@ func TestFuzzIsEncryptedFail1(t *testing.T) { // Test for trailer Prev entry pointing to an incorrect object type. func TestFuzzInvalidXrefPrev1(t *testing.T) { parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(` + parser.rs, parser.reader, parser.fileSize = makeReaderForText(` xref 0 1 0000000000 65535 f diff --git a/pdf/core/io.go b/pdf/core/io.go index fbfb28ec..40a85029 100644 --- a/pdf/core/io.go +++ b/pdf/core/io.go @@ -13,14 +13,17 @@ import ( "github.com/unidoc/unidoc/common" ) -func (this *PdfParser) ReadAtLeast(p []byte, n int) (int, error) { +// ReadAtLeast reads at least n bytes into slice p. +// Returns the number of bytes read (should always be == n), and an error on failure. +// TODO (v3): Unexport. +func (parser *PdfParser) ReadAtLeast(p []byte, n int) (int, error) { remaining := n start := 0 numRounds := 0 for remaining > 0 { - nRead, err := this.reader.Read(p[start:]) + nRead, err := parser.reader.Read(p[start:]) if err != nil { - common.Log.Error("ERROR Failed reading (%d;%d) err=%#v", nRead, numRounds, err) + common.Log.Debug("ERROR Failed reading (%d;%d) %s", nRead, numRounds, err.Error()) return start, errors.New("Failed reading") } numRounds++ @@ -31,14 +34,16 @@ func (this *PdfParser) ReadAtLeast(p []byte, n int) (int, error) { } // Get the current file offset, accounting for buffered position. -func (this *PdfParser) GetFileOffset() int64 { - offset, _ := this.rs.Seek(0, os.SEEK_CUR) - offset -= int64(this.reader.Buffered()) +// TODO (v3): Unexport. +func (parser *PdfParser) GetFileOffset() int64 { + offset, _ := parser.rs.Seek(0, os.SEEK_CUR) + offset -= int64(parser.reader.Buffered()) return offset } // Seek the file to an offset position. -func (this *PdfParser) SetFileOffset(offset int64) { - this.rs.Seek(offset, os.SEEK_SET) - this.reader = bufio.NewReader(this.rs) +// TODO (v3): Unexport. +func (parser *PdfParser) SetFileOffset(offset int64) { + parser.rs.Seek(offset, os.SEEK_SET) + parser.reader = bufio.NewReader(parser.rs) } diff --git a/pdf/core/parser.go b/pdf/core/parser.go index 69b05a70..f747a0f3 100644 --- a/pdf/core/parser.go +++ b/pdf/core/parser.go @@ -3,9 +3,6 @@ * file 'LICENSE.md', which is part of this source code package. */ -// The core package provides fundamental functionality for handling PDFs, including definitions of the core PDF objects -// (primitives), parsing a PDF file as a series of primitives, io, cross references, repairs, encryption, encoding and -// other core capabilities. package core import ( @@ -23,7 +20,6 @@ import ( "github.com/unidoc/unidoc/common" ) -var ObjCounts = map[int]int{} // Regular Expressions for parsing and identifying object signatures. var rePdfVersion = regexp.MustCompile(`%PDF-(\d)\.(\d)`) @@ -37,16 +33,18 @@ var reIndirectObject = regexp.MustCompile(`(\d+)\s+(\d+)\s+obj`) var reXrefSubsection = regexp.MustCompile(`(\d+)\s+(\d+)\s*$`) var reXrefEntry = regexp.MustCompile(`(\d+)\s+(\d+)\s+([nf])\s*$`) +// PdfParser parses a PDF file and provides access to the object structure of the PDF. type PdfParser struct { majorVersion int minorVersion int rs io.ReadSeeker reader *bufio.Reader + fileSize int64 xrefs XrefTable objstms ObjectStreams trailer *PdfObjectDictionary - ObjCache ObjectCache + ObjCache ObjectCache // TODO: Unexport (v3). crypter *PdfCrypt repairsAttempted bool // Avoid multiple attempts for repair. @@ -57,28 +55,32 @@ type PdfParser struct { streamLengthReferenceLookupInProgress map[int64]bool } -func (this *PdfParser) GetCrypter() *PdfCrypt { - return this.crypter +// GetCrypter returns the PdfCrypt instance which has information about the PDFs encryption. +func (parser *PdfParser) GetCrypter() *PdfCrypt { + return parser.crypter } -func (this *PdfParser) IsAuthenticated() bool { - return this.crypter.Authenticated +// IsAuthenticated returns true if the PDF has already been authenticated for accessing. +func (parser *PdfParser) IsAuthenticated() bool { + return parser.crypter.Authenticated } -func (this *PdfParser) GetTrailer() *PdfObjectDictionary { - return this.trailer +// GetTrailer returns the PDFs trailer dictionary. The trailer dictionary is typically the starting point for a PDF, +// referencing other key objects that are important in the document structure. +func (parser *PdfParser) GetTrailer() *PdfObjectDictionary { + return parser.trailer } // Skip over any spaces. -func (this *PdfParser) skipSpaces() (int, error) { +func (parser *PdfParser) skipSpaces() (int, error) { cnt := 0 for { - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err != nil { return 0, err } if IsWhiteSpace(bb[0]) { - this.reader.ReadByte() + parser.reader.ReadByte() cnt++ } else { break @@ -89,16 +91,16 @@ func (this *PdfParser) skipSpaces() (int, error) { } // Skip over comments and spaces. Can handle multi-line comments. -func (this *PdfParser) skipComments() error { - if _, err := this.skipSpaces(); err != nil { +func (parser *PdfParser) skipComments() error { + if _, err := parser.skipSpaces(); err != nil { return err } isFirst := true for { - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err != nil { - common.Log.Error("Error %s", err) + common.Log.Debug("Error %s", err.Error()) return err } if isFirst && bb[0] != '%' { @@ -108,30 +110,30 @@ func (this *PdfParser) skipComments() error { isFirst = false } if (bb[0] != '\r') && (bb[0] != '\n') { - this.reader.ReadByte() + parser.reader.ReadByte() } else { break } } // Call recursively to handle multiline comments. - return this.skipComments() + return parser.skipComments() } // Read a comment starting with '%'. -func (this *PdfParser) readComment() (string, error) { +func (parser *PdfParser) readComment() (string, error) { commentText := "" - _, err := this.skipSpaces() + _, err := parser.skipSpaces() if err != nil { return commentText, err } isFirst := true for { - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err != nil { - common.Log.Error("Error %s", err) + common.Log.Debug("Error %s", err.Error()) return commentText, err } if isFirst && bb[0] != '%' { @@ -140,7 +142,7 @@ func (this *PdfParser) readComment() (string, error) { isFirst = false } if (bb[0] != '\r') && (bb[0] != '\n') { - b, _ := this.reader.ReadByte() + b, _ := parser.reader.ReadByte() commentText += string(b) } else { break @@ -150,16 +152,16 @@ func (this *PdfParser) readComment() (string, error) { } // Read a single line of text from current position. -func (this *PdfParser) readTextLine() (string, error) { +func (parser *PdfParser) readTextLine() (string, error) { lineStr := "" for { - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err != nil { - common.Log.Error("Error %s", err) + common.Log.Debug("Error %s", err.Error()) return lineStr, err } if (bb[0] != '\r') && (bb[0] != '\n') { - b, _ := this.reader.ReadByte() + b, _ := parser.reader.ReadByte() lineStr += string(b) } else { break @@ -169,11 +171,11 @@ func (this *PdfParser) readTextLine() (string, error) { } // Parse a name starting with '/'. -func (this *PdfParser) parseName() (PdfObjectName, error) { +func (parser *PdfParser) parseName() (PdfObjectName, error) { name := "" nameStarted := false for { - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err == io.EOF { break // Can happen when loading from object stream. } @@ -185,12 +187,12 @@ func (this *PdfParser) parseName() (PdfObjectName, error) { // Should always start with '/', otherwise not valid. if bb[0] == '/' { nameStarted = true - this.reader.ReadByte() + parser.reader.ReadByte() } else if bb[0] == '%' { - this.readComment() - this.skipSpaces() + parser.readComment() + parser.skipSpaces() } else { - common.Log.Error("Error: Name starting with %s (% x)", bb, bb) + common.Log.Debug("ERROR Name starting with %s (% x)", bb, bb) return PdfObjectName(name), fmt.Errorf("Invalid name: (%c)", bb[0]) } } else { @@ -199,11 +201,11 @@ func (this *PdfParser) parseName() (PdfObjectName, error) { } else if (bb[0] == '/') || (bb[0] == '[') || (bb[0] == '(') || (bb[0] == ']') || (bb[0] == '<') || (bb[0] == '>') { break // Looks like start of next statement. } else if bb[0] == '#' { - hexcode, err := this.reader.Peek(3) + hexcode, err := parser.reader.Peek(3) if err != nil { return PdfObjectName(name), err } - this.reader.Discard(3) + parser.reader.Discard(3) code, err := hex.DecodeString(string(hexcode[1:3])) if err != nil { @@ -211,7 +213,7 @@ func (this *PdfParser) parseName() (PdfObjectName, error) { } name += string(code) } else { - b, _ := this.reader.ReadByte() + b, _ := parser.reader.ReadByte() name += string(b) } } @@ -239,13 +241,13 @@ func (this *PdfParser) parseName() (PdfObjectName, error) { // Nonetheless, we sometimes get numbers with exponential format, so // we will support it in the reader (no confusion with other types, so // no compromise). -func (this *PdfParser) parseNumber() (PdfObject, error) { +func (parser *PdfParser) parseNumber() (PdfObject, error) { isFloat := false allowSigns := true numStr := "" for { common.Log.Trace("Parsing number \"%s\"", numStr) - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err == io.EOF { // GH: EOF handling. Handle EOF like end of line. Can happen with // encoded object streams that the object is at the end. @@ -253,24 +255,24 @@ func (this *PdfParser) parseNumber() (PdfObject, error) { break // Handle like EOF } if err != nil { - common.Log.Error("ERROR %s", err) + common.Log.Debug("ERROR %s", err) return nil, err } if allowSigns && (bb[0] == '-' || bb[0] == '+') { // Only appear in the beginning, otherwise serves as a delimiter. - b, _ := this.reader.ReadByte() + b, _ := parser.reader.ReadByte() numStr += string(b) allowSigns = false // Only allowed in beginning, and after e (exponential). } else if IsDecimalDigit(bb[0]) { - b, _ := this.reader.ReadByte() + b, _ := parser.reader.ReadByte() numStr += string(b) } else if bb[0] == '.' { - b, _ := this.reader.ReadByte() + b, _ := parser.reader.ReadByte() numStr += string(b) isFloat = true } else if bb[0] == 'e' { // Exponential number format. - b, _ := this.reader.ReadByte() + b, _ := parser.reader.ReadByte() numStr += string(b) isFloat = true allowSigns = true @@ -292,27 +294,27 @@ func (this *PdfParser) parseNumber() (PdfObject, error) { } // A string starts with '(' and ends with ')'. -func (this *PdfParser) parseString() (PdfObjectString, error) { - this.reader.ReadByte() +func (parser *PdfParser) parseString() (PdfObjectString, error) { + parser.reader.ReadByte() bytes := []byte{} count := 1 for { - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err != nil { return PdfObjectString(bytes), err } if bb[0] == '\\' { // Escape sequence. - this.reader.ReadByte() // Skip the escape \ byte. - b, err := this.reader.ReadByte() + parser.reader.ReadByte() // Skip the escape \ byte. + b, err := parser.reader.ReadByte() if err != nil { return PdfObjectString(bytes), err } // Octal '\ddd' number (base 8). if IsOctalDigit(b) { - bb, err := this.reader.Peek(2) + bb, err := parser.reader.Peek(2) if err != nil { return PdfObjectString(bytes), err } @@ -326,7 +328,7 @@ func (this *PdfParser) parseString() (PdfObjectString, error) { break } } - this.reader.Discard(len(numeric) - 1) + parser.reader.Discard(len(numeric) - 1) common.Log.Trace("Numeric string \"%s\"", numeric) code, err := strconv.ParseUint(string(numeric), 8, 32) @@ -362,12 +364,12 @@ func (this *PdfParser) parseString() (PdfObjectString, error) { } else if bb[0] == ')' { count-- if count == 0 { - this.reader.ReadByte() + parser.reader.ReadByte() break } } - b, _ := this.reader.ReadByte() + b, _ := parser.reader.ReadByte() bytes = append(bytes, b) } @@ -376,26 +378,26 @@ func (this *PdfParser) parseString() (PdfObjectString, error) { // Starts with '<' ends with '>'. // Currently not converting the hex codes to characters. -func (this *PdfParser) parseHexString() (PdfObjectString, error) { - this.reader.ReadByte() +func (parser *PdfParser) parseHexString() (PdfObjectString, error) { + parser.reader.ReadByte() hextable := []byte("0123456789abcdefABCDEF") tmp := []byte{} for { - this.skipSpaces() + parser.skipSpaces() - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err != nil { return PdfObjectString(""), err } if bb[0] == '>' { - this.reader.ReadByte() + parser.reader.ReadByte() break } - b, _ := this.reader.ReadByte() + b, _ := parser.reader.ReadByte() if bytes.IndexByte(hextable, b) >= 0 { tmp = append(tmp, b) } @@ -410,25 +412,25 @@ func (this *PdfParser) parseHexString() (PdfObjectString, error) { } // Starts with '[' ends with ']'. Can contain any kinds of direct objects. -func (this *PdfParser) parseArray() (PdfObjectArray, error) { +func (parser *PdfParser) parseArray() (PdfObjectArray, error) { arr := make(PdfObjectArray, 0) - this.reader.ReadByte() + parser.reader.ReadByte() for { - this.skipSpaces() + parser.skipSpaces() - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err != nil { return arr, err } if bb[0] == ']' { - this.reader.ReadByte() + parser.reader.ReadByte() break } - obj, err := this.parseObject() + obj, err := parser.parseObject() if err != nil { return arr, err } @@ -439,22 +441,22 @@ func (this *PdfParser) parseArray() (PdfObjectArray, error) { } // Parse bool object. -func (this *PdfParser) parseBool() (PdfObjectBool, error) { - bb, err := this.reader.Peek(4) +func (parser *PdfParser) parseBool() (PdfObjectBool, error) { + bb, err := parser.reader.Peek(4) if err != nil { return PdfObjectBool(false), err } if (len(bb) >= 4) && (string(bb[:4]) == "true") { - this.reader.Discard(4) + parser.reader.Discard(4) return PdfObjectBool(true), nil } - bb, err = this.reader.Peek(5) + bb, err = parser.reader.Peek(5) if err != nil { return PdfObjectBool(false), err } if (len(bb) >= 5) && (string(bb[:5]) == "false") { - this.reader.Discard(5) + parser.reader.Discard(5) return PdfObjectBool(false), nil } @@ -467,7 +469,7 @@ func parseReference(refStr string) (PdfObjectReference, error) { result := reReference.FindStringSubmatch(string(refStr)) if len(result) < 3 { - common.Log.Error("Error parsing reference") + common.Log.Debug("Error parsing reference") return objref, errors.New("Unable to parse reference") } @@ -480,18 +482,18 @@ func parseReference(refStr string) (PdfObjectReference, error) { } // Parse null object. -func (this *PdfParser) parseNull() (PdfObjectNull, error) { - _, err := this.reader.Discard(4) +func (parser *PdfParser) parseNull() (PdfObjectNull, error) { + _, err := parser.reader.Discard(4) return PdfObjectNull{}, err } // Detect the signature at the current file position and parse // the corresponding object. -func (this *PdfParser) parseObject() (PdfObject, error) { +func (parser *PdfParser) parseObject() (PdfObject, error) { common.Log.Trace("Read direct object") - this.skipSpaces() + parser.skipSpaces() for { - bb, err := this.reader.Peek(2) + bb, err := parser.reader.Peek(2) if err != nil { return nil, err } @@ -499,52 +501,52 @@ func (this *PdfParser) parseObject() (PdfObject, error) { common.Log.Trace("Peek string: %s", string(bb)) // Determine type. if bb[0] == '/' { - name, err := this.parseName() + name, err := parser.parseName() common.Log.Trace("->Name: '%s'", name) return &name, err } else if bb[0] == '(' { common.Log.Trace("->String!") - str, err := this.parseString() + str, err := parser.parseString() return &str, err } else if bb[0] == '[' { common.Log.Trace("->Array!") - arr, err := this.parseArray() + arr, err := parser.parseArray() return &arr, err } else if (bb[0] == '<') && (bb[1] == '<') { common.Log.Trace("->Dict!") - dict, err := this.ParseDict() + dict, err := parser.ParseDict() return dict, err } else if bb[0] == '<' { common.Log.Trace("->Hex string!") - str, err := this.parseHexString() + str, err := parser.parseHexString() return &str, err } else if bb[0] == '%' { - this.readComment() - this.skipSpaces() + parser.readComment() + parser.skipSpaces() } else { common.Log.Trace("->Number or ref?") // Reference or number? // Let's peek farther to find out. - bb, _ = this.reader.Peek(15) + bb, _ = parser.reader.Peek(15) peekStr := string(bb) common.Log.Trace("Peek str: %s", peekStr) if (len(peekStr) > 3) && (peekStr[:4] == "null") { - null, err := this.parseNull() + null, err := parser.parseNull() return &null, err } else if (len(peekStr) > 4) && (peekStr[:5] == "false") { - b, err := this.parseBool() + b, err := parser.parseBool() return &b, err } else if (len(peekStr) > 3) && (peekStr[:4] == "true") { - b, err := this.parseBool() + b, err := parser.parseBool() return &b, err } // Match reference. result1 := reReference.FindStringSubmatch(string(peekStr)) if len(result1) > 1 { - bb, _ = this.reader.ReadBytes('R') - common.Log.Trace("-> !Ref: '%s'", string(bb[:len(bb)])) + bb, _ = parser.reader.ReadBytes('R') + common.Log.Trace("-> !Ref: '%s'", string(bb[:])) ref, err := parseReference(string(bb)) return &ref, err } @@ -553,7 +555,7 @@ func (this *PdfParser) parseObject() (PdfObject, error) { if len(result2) > 1 { // Number object. common.Log.Trace("-> Number!") - num, err := this.parseNumber() + num, err := parser.parseNumber() return num, err } @@ -562,39 +564,38 @@ func (this *PdfParser) parseObject() (PdfObject, error) { // Number object (exponential) common.Log.Trace("-> Exponential Number!") common.Log.Trace("% s", result2) - num, err := this.parseNumber() + num, err := parser.parseNumber() return num, err } - common.Log.Error("Unknown (peek %q)", peekStr) + common.Log.Debug("ERROR Unknown (peek \"%s\")", peekStr) return nil, errors.New("Object parsing error - unexpected pattern") } } - - return nil, errors.New("Object parsing error - unexpected pattern") } // Reads and parses a PDF dictionary object enclosed with '<<' and '>>' -func (this *PdfParser) ParseDict() (*PdfObjectDictionary, error) { +// TODO: Unexport (v3). +func (parser *PdfParser) ParseDict() (*PdfObjectDictionary, error) { common.Log.Trace("Reading PDF Dict!") dict := MakeDict() // Pass the '<<' - c, _ := this.reader.ReadByte() + c, _ := parser.reader.ReadByte() if c != '<' { return nil, errors.New("Invalid dict") } - c, _ = this.reader.ReadByte() + c, _ = parser.reader.ReadByte() if c != '<' { return nil, errors.New("Invalid dict") } for { - this.skipSpaces() - this.skipComments() + parser.skipSpaces() + parser.skipComments() - bb, err := this.reader.Peek(2) + bb, err := parser.reader.Peek(2) if err != nil { return nil, err } @@ -602,16 +603,16 @@ func (this *PdfParser) ParseDict() (*PdfObjectDictionary, error) { common.Log.Trace("Dict peek: %s (% x)!", string(bb), string(bb)) if (bb[0] == '>') && (bb[1] == '>') { common.Log.Trace("EOF dictionary") - this.reader.ReadByte() - this.reader.ReadByte() + parser.reader.ReadByte() + parser.reader.ReadByte() break } common.Log.Trace("Parse the name!") - keyName, err := this.parseName() + keyName, err := parser.parseName() common.Log.Trace("Key: %s", keyName) if err != nil { - common.Log.Error("Could not parse name err=%#v", err) + common.Log.Debug("ERROR Returning name err %s", err) return nil, err } @@ -620,18 +621,18 @@ func (this *PdfParser) ParseDict() (*PdfObjectDictionary, error) { // space. For example "\Boundsnull" newKey := keyName[0 : len(keyName)-4] common.Log.Debug("Taking care of null bug (%s)", keyName) - common.Log.Debug("New key %q = null", newKey) - this.skipSpaces() - bb, _ := this.reader.Peek(1) + common.Log.Debug("New key \"%s\" = null", newKey) + parser.skipSpaces() + bb, _ := parser.reader.Peek(1) if bb[0] == '/' { dict.Set(newKey, MakeNull()) continue } } - this.skipSpaces() + parser.skipSpaces() - val, err := this.parseObject() + val, err := parser.parseObject() if err != nil { return nil, err } @@ -641,29 +642,27 @@ func (this *PdfParser) ParseDict() (*PdfObjectDictionary, error) { } common.Log.Trace("returning PDF Dict!") - return dict, nil } // Parse the pdf version from the beginning of the file. // Returns the major and minor parts of the version. // E.g. for "PDF-1.7" would return 1 and 7. -func (this *PdfParser) parsePdfVersion() (int, int, error) { - this.rs.Seek(0, os.SEEK_SET) +func (parser *PdfParser) parsePdfVersion() (int, int, error) { + parser.rs.Seek(0, os.SEEK_SET) var offset int64 = 20 b := make([]byte, offset) - this.rs.Read(b) + parser.rs.Read(b) result1 := rePdfVersion.FindStringSubmatch(string(b)) if len(result1) < 3 { - major, minor, err := this.seekPdfVersionTopDown() - if err == nil { + major, minor, err := parser.seekPdfVersionTopDown() + if err != nil { common.Log.Debug("Failed recovery - unable to find version") return 0, 0, err } return major, minor, nil - return 0, 0, errors.New("PDF version not found") } majorVersion, err := strconv.ParseInt(result1[1], 10, 64) @@ -683,10 +682,10 @@ func (this *PdfParser) parsePdfVersion() (int, int, error) { } // Conventional xref table starting with 'xref'. -func (this *PdfParser) parseXrefTable() (*PdfObjectDictionary, error) { +func (parser *PdfParser) parseXrefTable() (*PdfObjectDictionary, error) { var trailer *PdfObjectDictionary - txt, err := this.readTextLine() + txt, err := parser.readTextLine() if err != nil { return nil, err } @@ -696,13 +695,13 @@ func (this *PdfParser) parseXrefTable() (*PdfObjectDictionary, error) { secObjects := 0 insideSubsection := false for { - this.skipSpaces() - _, err := this.reader.Peek(1) + parser.skipSpaces() + _, err := parser.reader.Peek(1) if err != nil { return nil, err } - txt, err = this.readTextLine() + txt, err = parser.readTextLine() if err != nil { return nil, err } @@ -721,7 +720,7 @@ func (this *PdfParser) parseXrefTable() (*PdfObjectDictionary, error) { result2 := reXrefEntry.FindStringSubmatch(txt) if len(result2) == 4 { if insideSubsection == false { - common.Log.Error("Xref invalid format!\n") + common.Log.Debug("ERROR Xref invalid format!\n") return nil, errors.New("Xref invalid format") } @@ -744,12 +743,12 @@ func (this *PdfParser) parseXrefTable() (*PdfObjectDictionary, error) { // Load if not existing or higher generation number than previous. // Usually should not happen, lower generation numbers // would be marked as free. But can still happen! - x, ok := this.xrefs[curObjNum] + x, ok := parser.xrefs[curObjNum] if !ok || gen > x.generation { obj := XrefObject{objectNumber: curObjNum, xtype: XREF_TABLE_ENTRY, offset: first, generation: gen} - this.xrefs[curObjNum] = obj + parser.xrefs[curObjNum] = obj } } @@ -761,26 +760,26 @@ func (this *PdfParser) parseXrefTable() (*PdfObjectDictionary, error) { // Sometimes get "trailer << ...." // Need to rewind to end of trailer text. if len(txt) > 9 { - offset := this.GetFileOffset() - this.SetFileOffset(offset - int64(len(txt)) + 7) + offset := parser.GetFileOffset() + parser.SetFileOffset(offset - int64(len(txt)) + 7) } - this.skipSpaces() - this.skipComments() + parser.skipSpaces() + parser.skipComments() common.Log.Trace("Reading trailer dict!") common.Log.Trace("peek: \"%s\"", txt) - trailer, err = this.ParseDict() + trailer, err = parser.ParseDict() common.Log.Trace("EOF reading trailer dict!") if err != nil { - common.Log.Error("Error parsing trailer dict (%s)", err) + common.Log.Debug("Error parsing trailer dict (%s)", err) return nil, err } break } if txt == "%%EOF" { - common.Log.Error("End of file - trailer not found - error!") - return nil, errors.New("End of file - trailer not found!") + common.Log.Debug("ERROR: end of file - trailer not found - error!") + return nil, errors.New("End of file - trailer not found") } common.Log.Trace("xref more : %s", txt) @@ -792,33 +791,38 @@ func (this *PdfParser) parseXrefTable() (*PdfObjectDictionary, error) { // Load the cross references from an xref stream object (XRefStm). // Also load the dictionary information (trailer dictionary). -func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictionary, error) { +func (parser *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictionary, error) { if xstm != nil { common.Log.Trace("XRefStm xref table object at %d", xstm) - this.rs.Seek(int64(*xstm), os.SEEK_SET) - this.reader = bufio.NewReader(this.rs) + parser.rs.Seek(int64(*xstm), os.SEEK_SET) + parser.reader = bufio.NewReader(parser.rs) } - xrefObj, err := this.ParseIndirectObject() + xrefObj, err := parser.ParseIndirectObject() if err != nil { - common.Log.Error("Failed to read xref object") + common.Log.Debug("ERROR: Failed to read xref object") return nil, errors.New("Failed to read xref object") } common.Log.Trace("XRefStm object: %s", xrefObj) xs, ok := xrefObj.(*PdfObjectStream) if !ok { - common.Log.Error("XRefStm pointing to non-stream object!") - return nil, errors.New("XRefStm pointing to a non-stream object!") + common.Log.Debug("ERROR: XRefStm pointing to non-stream object!") + return nil, errors.New("XRefStm pointing to a non-stream object") } trailerDict := xs.PdfObjectDictionary sizeObj, ok := xs.PdfObjectDictionary.Get("Size").(*PdfObjectInteger) if !ok { - common.Log.Error("Missing size from xref stm") + common.Log.Debug("ERROR: Missing size from xref stm") return nil, errors.New("Missing Size from xref stm") } + // Sanity check to avoid DoS attacks. Maximum number of indirect objects on 32 bit system. + if int64(*sizeObj) > 8388607 { + common.Log.Debug("ERROR: xref Size exceeded limit, over 8388607 (%d)", *sizeObj) + return nil, errors.New("Range check error") + } wObj := xs.PdfObjectDictionary.Get("W") wArr, ok := wObj.(*PdfObjectArray) @@ -828,7 +832,7 @@ func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictio wLen := len(*wArr) if wLen != 3 { - common.Log.Error("Unsupported xref stm (len(W) != 3 - %d)", wLen) + common.Log.Debug("ERROR: Unsupported xref stm (len(W) != 3 - %d)", wLen) return nil, errors.New("Unsupported xref stm len(W) != 3") } @@ -851,13 +855,21 @@ func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictio common.Log.Debug("ERROR: Unable to decode stream: %v", err) return nil, err } - common.Log.Debug("@@ ds=%d %T %+v\n", len(ds), ds, ds) s0 := int(b[0]) s1 := int(b[0] + b[1]) s2 := int(b[0] + b[1] + b[2]) deltab := int(b[0] + b[1] + b[2]) + if s0 < 0 || s1 < 0 || s2 < 0 { + common.Log.Debug("Error s value < 0 (%d,%d,%d)", s0, s1, s2) + return nil, errors.New("Range check error") + } + if deltab == 0 { + common.Log.Debug("No xref objects in stream (deltab == 0)") + return trailerDict, nil + } + // Calculate expected entries. entries := len(ds) / deltab @@ -877,23 +889,31 @@ func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictio indexList := []int{} if indexObj != nil { common.Log.Trace("Index: %b", indexObj) - indices, ok := indexObj.(*PdfObjectArray) + indicesArray, ok := indexObj.(*PdfObjectArray) if !ok { common.Log.Debug("Invalid Index object (should be an array)") return nil, errors.New("Invalid Index object") } // Expect indLen to be a multiple of 2. - if len(*indices)%2 != 0 { + if len(*indicesArray)%2 != 0 { common.Log.Debug("WARNING Failure loading xref stm index not multiple of 2.") return nil, errors.New("Range check error") } objCount = 0 - for i := 0; i < len(*indices); i += 2 { + + indices, err := indicesArray.ToIntegerArray() + if err != nil { + common.Log.Debug("Error getting index array as integers: %v", err) + return nil, err + } + + for i := 0; i < len(indices); i += 2 { // add the indices to the list.. - startIdx := int(*(*indices)[i].(*PdfObjectInteger)) - numObjs := int(*(*indices)[i+1].(*PdfObjectInteger)) + + startIdx := indices[i] + numObjs := indices[i+1] for j := 0; j < numObjs; j++ { indexList = append(indexList, startIdx+j) } @@ -916,7 +936,7 @@ func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictio if entries != len(indexList) { // If mismatch -> error (already allowing mismatch of 1 if Index not specified). - common.Log.Error("xref stm: num entries != len(indices) (%d != %d)", entries, len(indexList)) + common.Log.Debug("ERROR: xref stm: num entries != len(indices) (%d != %d)", entries, len(indexList)) return nil, errors.New("Xref stm num entries != len(indices)") } @@ -928,7 +948,6 @@ func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictio var tmp int64 = 0 for i := 0; i < len(v); i++ { tmp += int64(v[i]) * (1 << uint(8*(len(v)-i-1))) - } return tmp } @@ -936,9 +955,27 @@ func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictio common.Log.Trace("Decoded stream length: %d", len(ds)) objIndex := 0 for i := 0; i < len(ds); i += deltab { + err := checkBounds(len(ds), i, i+s0) + if err != nil { + common.Log.Debug("Invalid slice range: %v", err) + return nil, err + } p1 := ds[i : i+s0] + + err = checkBounds(len(ds), i+s0, i+s1) + if err != nil { + common.Log.Debug("Invalid slice range: %v", err) + return nil, err + } p2 := ds[i+s0 : i+s1] + + err = checkBounds(len(ds), i+s1, i+s2) + if err != nil { + common.Log.Debug("Invalid slice range: %v", err) + return nil, err + } p3 := ds[i+s1 : i+s2] + ftype := convertBytes(p1) n2 := convertBytes(p2) n3 := convertBytes(p3) @@ -949,6 +986,10 @@ func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictio ftype = 1 } + if objIndex >= len(indexList) { + common.Log.Debug("XRef stream - Trying to access index out of bounds - breaking") + break + } objNum := indexList[objIndex] objIndex++ @@ -963,24 +1004,24 @@ func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictio common.Log.Trace("- In use - uncompressed via offset %b", p2) // Object type 1: Objects that are in use but are not // compressed, i.e. defined by an offset (normal entry) - if xr, ok := this.xrefs[objNum]; !ok || int(n3) > xr.generation { + if xr, ok := parser.xrefs[objNum]; !ok || int(n3) > xr.generation { // Only overload if not already loaded! // or has a newer generation number. (should not happen) obj := XrefObject{objectNumber: objNum, xtype: XREF_TABLE_ENTRY, offset: n2, generation: int(n3)} - this.xrefs[objNum] = obj + parser.xrefs[objNum] = obj } } else if ftype == 2 { // Object type 2: Compressed object. common.Log.Trace("- In use - compressed object") - if _, ok := this.xrefs[objNum]; !ok { + if _, ok := parser.xrefs[objNum]; !ok { obj := XrefObject{objectNumber: objNum, xtype: XREF_OBJECT_STREAM, osObjNumber: int(n2), osObjIndex: int(n3)} - this.xrefs[objNum] = obj - common.Log.Trace("entry: %s", this.xrefs[objNum]) + parser.xrefs[objNum] = obj + common.Log.Trace("entry: %s", parser.xrefs[objNum]) } } else { - common.Log.Error("--------INVALID TYPE XrefStm invalid?-------") + common.Log.Debug("ERROR: --------INVALID TYPE XrefStm invalid?-------") // Continue, we do not define anything -> null object. // 7.5.8.3: // @@ -997,35 +1038,35 @@ func (this *PdfParser) parseXrefStream(xstm *PdfObjectInteger) (*PdfObjectDictio // Parse xref table at the current file position. Can either be a // standard xref table, or an xref stream. -func (this *PdfParser) parseXref() (*PdfObjectDictionary, error) { +func (parser *PdfParser) parseXref() (*PdfObjectDictionary, error) { var err error var trailerDict *PdfObjectDictionary // Points to xref table or xref stream object? - bb, _ := this.reader.Peek(20) + bb, _ := parser.reader.Peek(20) if reIndirectObject.MatchString(string(bb)) { common.Log.Trace("xref points to an object. Probably xref object") common.Log.Trace("starting with \"%s\"", string(bb)) - trailerDict, err = this.parseXrefStream(nil) + trailerDict, err = parser.parseXrefStream(nil) if err != nil { return nil, err } } else if reXrefTable.MatchString(string(bb)) { common.Log.Trace("Standard xref section table!") var err error - trailerDict, err = this.parseXrefTable() + trailerDict, err = parser.parseXrefTable() if err != nil { return nil, err } } else { common.Log.Debug("Warning: Unable to find xref table or stream. Repair attempted: Looking for earliest xref from bottom.") - err := this.repairSeekXrefMarker() + err := parser.repairSeekXrefMarker() if err != nil { common.Log.Debug("Repair failed - %v", err) return nil, err } - trailerDict, err = this.parseXrefTable() + trailerDict, err = parser.parseXrefTable() if err != nil { return nil, err } @@ -1036,7 +1077,7 @@ func (this *PdfParser) parseXref() (*PdfObjectDictionary, error) { // Look for EOF marker and seek to its beginning. // Define an offset position from the end of the file. -func (this *PdfParser) seekToEOFMarker(fSize int64) error { +func (parser *PdfParser) seekToEOFMarker(fSize int64) error { // Define the starting point (from the end of the file) to search from. var offset int64 = 0 @@ -1049,21 +1090,21 @@ func (this *PdfParser) seekToEOFMarker(fSize int64) error { } // Move back enough (as we need to read forward). - _, err := this.rs.Seek(-offset-buflen, io.SeekEnd) + _, err := parser.rs.Seek(-offset-buflen, io.SeekEnd) if err != nil { return err } // Read the data. b1 := make([]byte, buflen) - this.rs.Read(b1) + parser.rs.Read(b1) common.Log.Trace("Looking for EOF marker: \"%s\"", string(b1)) ind := reEOF.FindAllStringIndex(string(b1), -1) if ind != nil { // Found it. lastInd := ind[len(ind)-1] common.Log.Trace("Ind: % d", ind) - this.rs.Seek(-offset-buflen+int64(lastInd[0]), io.SeekEnd) + parser.rs.Seek(-offset-buflen+int64(lastInd[0]), io.SeekEnd) return nil } else { common.Log.Debug("Warning: EOF marker not found! - continue seeking") @@ -1092,29 +1133,30 @@ func (this *PdfParser) seekToEOFMarker(fSize int64) error { // 3. Check the Prev xref // 4. Continue looking for Prev until not found. // -// The earlier xrefs have higher precedence. If objects are already -// loaded then older versions of them will be ignored. +// The earlier xrefs have higher precedence. If objects already +// loaded will ignore older versions. // -func (this *PdfParser) loadXrefs() (*PdfObjectDictionary, error) { - this.xrefs = make(XrefTable) - this.objstms = make(ObjectStreams) +func (parser *PdfParser) loadXrefs() (*PdfObjectDictionary, error) { + parser.xrefs = make(XrefTable) + parser.objstms = make(ObjectStreams) // Get the file size. - fSize, err := this.rs.Seek(0, io.SeekEnd) + fSize, err := parser.rs.Seek(0, io.SeekEnd) if err != nil { return nil, err } common.Log.Trace("fsize: %d", fSize) + parser.fileSize = fSize // Seek the EOF marker. - err = this.seekToEOFMarker(fSize) + err = parser.seekToEOFMarker(fSize) if err != nil { common.Log.Debug("Failed seek to eof marker: %v", err) return nil, err } // Look for startxref and get the xref offset. - curOffset, err := this.rs.Seek(0, io.SeekCurrent) + curOffset, err := parser.rs.Seek(0, io.SeekCurrent) if err != nil { return nil, err } @@ -1125,13 +1167,13 @@ func (this *PdfParser) loadXrefs() (*PdfObjectDictionary, error) { if offset < 0 { offset = 0 } - _, err = this.rs.Seek(offset, io.SeekStart) + _, err = parser.rs.Seek(offset, io.SeekStart) if err != nil { return nil, err } b2 := make([]byte, numBytes) - _, err = this.rs.Read(b2) + _, err = parser.rs.Read(b2) if err != nil { common.Log.Debug("Failed reading while looking for startxref: %v", err) return nil, err @@ -1150,19 +1192,19 @@ func (this *PdfParser) loadXrefs() (*PdfObjectDictionary, error) { common.Log.Trace("startxref at %d", offsetXref) if offsetXref > fSize { - common.Log.Error("Xref offset outside of file") + common.Log.Debug("ERROR: Xref offset outside of file") common.Log.Debug("Attempting repair") - offsetXref, err = this.repairLocateXref() + offsetXref, err = parser.repairLocateXref() if err != nil { - common.Log.Error("Repair attempt failed (%s)") + common.Log.Debug("ERROR: Repair attempt failed (%s)") return nil, err } } // Read the xref. - this.rs.Seek(int64(offsetXref), io.SeekStart) - this.reader = bufio.NewReader(this.rs) + parser.rs.Seek(int64(offsetXref), io.SeekStart) + parser.reader = bufio.NewReader(parser.rs) - trailerDict, err := this.parseXref() + trailerDict, err := parser.parseXref() if err != nil { return nil, err } @@ -1174,7 +1216,7 @@ func (this *PdfParser) loadXrefs() (*PdfObjectDictionary, error) { if !ok { return nil, errors.New("XRefStm != int") } - _, err = this.parseXrefStream(xo) + _, err = parser.parseXrefStream(xo) if err != nil { return nil, err } @@ -1207,10 +1249,10 @@ func (this *PdfParser) loadXrefs() (*PdfObjectDictionary, error) { common.Log.Trace("Another Prev xref table object at %d", off) // Can be either regular table, or an xref object... - this.rs.Seek(int64(off), os.SEEK_SET) - this.reader = bufio.NewReader(this.rs) + parser.rs.Seek(int64(off), os.SEEK_SET) + parser.reader = bufio.NewReader(parser.rs) - ptrailerDict, err := this.parseXref() + ptrailerDict, err := parser.parseXref() if err != nil { common.Log.Debug("Warning: Error - Failed loading another (Prev) trailer") common.Log.Debug("Attempting to continue by ignoring it") @@ -1233,9 +1275,9 @@ func (this *PdfParser) loadXrefs() (*PdfObjectDictionary, error) { } // Return the closest object following offset from the xrefs table. -func (this *PdfParser) xrefNextObjectOffset(offset int64) int64 { +func (parser *PdfParser) xrefNextObjectOffset(offset int64) int64 { nextOffset := int64(0) - for _, xref := range this.xrefs { + for _, xref := range parser.xrefs { if xref.offset > offset && (xref.offset < nextOffset || nextOffset == 0) { nextOffset = xref.offset } @@ -1245,19 +1287,19 @@ func (this *PdfParser) xrefNextObjectOffset(offset int64) int64 { // Get stream length, avoiding recursive loops. // The input is the PdfObject that is to be traced to a direct object. -func (this *PdfParser) traceStreamLength(lengthObj PdfObject) (PdfObject, error) { +func (parser *PdfParser) traceStreamLength(lengthObj PdfObject) (PdfObject, error) { lengthRef, isRef := lengthObj.(*PdfObjectReference) if isRef { - lookupInProgress, has := this.streamLengthReferenceLookupInProgress[lengthRef.ObjectNumber] + lookupInProgress, has := parser.streamLengthReferenceLookupInProgress[lengthRef.ObjectNumber] if has && lookupInProgress { common.Log.Debug("Stream Length reference unresolved (illegal)") return nil, errors.New("Illegal recursive loop") } // Mark lookup as in progress. - this.streamLengthReferenceLookupInProgress[lengthRef.ObjectNumber] = true + parser.streamLengthReferenceLookupInProgress[lengthRef.ObjectNumber] = true } - slo, err := this.Trace(lengthObj) + slo, err := parser.Trace(lengthObj) if err != nil { return nil, err } @@ -1265,19 +1307,20 @@ func (this *PdfParser) traceStreamLength(lengthObj PdfObject) (PdfObject, error) if isRef { // Mark as completed lookup - this.streamLengthReferenceLookupInProgress[lengthRef.ObjectNumber] = false + parser.streamLengthReferenceLookupInProgress[lengthRef.ObjectNumber] = false } return slo, nil } -// Parse an indirect object from the input stream. -// Can also be an object stream. -func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { +// Parse an indirect object from the input stream. Can also be an object stream. +// Returns the indirect object (*PdfIndirectObject) or the stream object (*PdfObjectStream). +// TODO: Unexport (v3). +func (parser *PdfParser) ParseIndirectObject() (PdfObject, error) { indirect := PdfIndirectObject{} common.Log.Trace("-Read indirect obj") - bb, err := this.reader.Peek(20) + bb, err := parser.reader.Peek(20) if err != nil { common.Log.Debug("ERROR: Fail to read indirect obj") return &indirect, err @@ -1289,13 +1332,13 @@ func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { common.Log.Debug("ERROR: Unable to find object signature (%s)", string(bb)) return &indirect, errors.New("Unable to detect indirect object signature") } - this.reader.Discard(indices[0]) // Take care of any small offset. + parser.reader.Discard(indices[0]) // Take care of any small offset. common.Log.Trace("Offsets % d", indices) // Read the object header. hlen := indices[1] - indices[0] hb := make([]byte, hlen) - _, err = this.ReadAtLeast(hb, hlen) + _, err = parser.ReadAtLeast(hb, hlen) if err != nil { common.Log.Debug("ERROR: unable to read - %s", err) return nil, err @@ -1304,7 +1347,7 @@ func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { result := reIndirectObject.FindStringSubmatch(string(hb)) if len(result) < 3 { - common.Log.Error("Unable to find object signature (%s)", string(hb)) + common.Log.Debug("ERROR: Unable to find object signature (%s)", string(hb)) return &indirect, errors.New("Unable to detect indirect object signature") } @@ -1313,36 +1356,35 @@ func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { indirect.ObjectNumber = int64(on) indirect.GenerationNumber = int64(gn) - ObjCounts[on]++ for { - bb, err := this.reader.Peek(2) + bb, err := parser.reader.Peek(2) if err != nil { return &indirect, err } common.Log.Trace("Ind. peek: %s (% x)!", string(bb), string(bb)) if IsWhiteSpace(bb[0]) { - this.skipSpaces() + parser.skipSpaces() } else if bb[0] == '%' { - this.skipComments() + parser.skipComments() } else if (bb[0] == '<') && (bb[1] == '<') { common.Log.Trace("Call ParseDict") - indirect.PdfObject, err = this.ParseDict() + indirect.PdfObject, err = parser.ParseDict() common.Log.Trace("EOF Call ParseDict: %v", err) if err != nil { return &indirect, err } common.Log.Trace("Parsed dictionary... finished.") } else if (bb[0] == '/') || (bb[0] == '(') || (bb[0] == '[') || (bb[0] == '<') { - indirect.PdfObject, err = this.parseObject() + indirect.PdfObject, err = parser.parseObject() if err != nil { return &indirect, err } common.Log.Trace("Parsed object ... finished.") } else { if bb[0] == 'e' { - lineStr, err := this.readTextLine() + lineStr, err := parser.readTextLine() if err != nil { return nil, err } @@ -1350,7 +1392,7 @@ func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { break } } else if bb[0] == 's' { - bb, _ = this.reader.Peek(10) + bb, _ = parser.reader.Peek(10) if string(bb[:6]) == "stream" { discardBytes := 6 if len(bb) > 6 { @@ -1370,7 +1412,7 @@ func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { } } - this.reader.Discard(discardBytes) + parser.reader.Discard(discardBytes) dict, isDict := indirect.PdfObject.(*PdfObjectDictionary) if !isDict { @@ -1379,7 +1421,7 @@ func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { common.Log.Trace("Stream dict %s", dict) // Special stream length tracing function used to avoid endless recursive looping. - slo, err := this.traceStreamLength(dict.Get("Length")) + slo, err := parser.traceStreamLength(dict.Get("Length")) if err != nil { common.Log.Debug("Fail to trace stream length: %v", err) return nil, err @@ -1398,8 +1440,8 @@ func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { // Validate the stream length based on the cross references. // Find next object with closest offset to current object and calculate // the expected stream length based on that. - streamStartOffset := this.GetFileOffset() - nextObjectOffset := this.xrefNextObjectOffset(streamStartOffset) + streamStartOffset := parser.GetFileOffset() + nextObjectOffset := parser.xrefNextObjectOffset(streamStartOffset) if streamStartOffset+int64(streamLength) > nextObjectOffset && nextObjectOffset > streamStartOffset { common.Log.Debug("Expected ending at %d", streamStartOffset+int64(streamLength)) common.Log.Debug("Next object starting at %d", nextObjectOffset) @@ -1414,28 +1456,34 @@ func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { dict.Set("Length", MakeInteger(newLength)) } + // Make sure is less than actual file size. + if int64(streamLength) > parser.fileSize { + common.Log.Debug("ERROR: Stream length cannot be larger than file size") + return nil, errors.New("Invalid stream length, larger than file size") + } + stream := make([]byte, streamLength) - _, err = this.ReadAtLeast(stream, int(streamLength)) + _, err = parser.ReadAtLeast(stream, int(streamLength)) if err != nil { common.Log.Debug("ERROR stream (%d): %X", len(stream), stream) common.Log.Debug("ERROR: %v", err) return nil, err } - streamobj := PdfObjectStream{} // !@#$ + streamobj := PdfObjectStream{} streamobj.Stream = stream streamobj.PdfObjectDictionary = indirect.PdfObject.(*PdfObjectDictionary) streamobj.ObjectNumber = indirect.ObjectNumber streamobj.GenerationNumber = indirect.GenerationNumber - this.skipSpaces() - this.reader.Discard(9) // endstream - this.skipSpaces() + parser.skipSpaces() + parser.reader.Discard(9) // endstream + parser.skipSpaces() return &streamobj, nil } } - indirect.PdfObject, err = this.parseObject() + indirect.PdfObject, err = parser.parseObject() return &indirect, err } } @@ -1444,6 +1492,7 @@ func (this *PdfParser) ParseIndirectObject() (PdfObject, error) { } // For testing purposes. +// TODO: Unexport (v3) or move to test files, if needed by external test cases. func NewParserFromString(txt string) *PdfParser { parser := PdfParser{} buf := []byte(txt) @@ -1454,11 +1503,13 @@ func NewParserFromString(txt string) *PdfParser { bufferedReader := bufio.NewReader(bufReader) parser.reader = bufferedReader + parser.fileSize = int64(len(txt)) + return &parser } -// Creates a new parser for a PDF file via ReadSeeker. Loads the -// cross reference stream and trailer. +// NewParser creates a new parser for a PDF file via ReadSeeker. Loads the cross reference stream and trailer. +// An error is returned on failure. func NewParser(rs io.ReadSeeker) (*PdfParser, error) { parser := &PdfParser{} @@ -1466,22 +1517,19 @@ func NewParser(rs io.ReadSeeker) (*PdfParser, error) { parser.ObjCache = make(ObjectCache) parser.streamLengthReferenceLookupInProgress = map[int64]bool{} - // Start by reading xrefs from bottom + // Start by reading the xrefs (from bottom). trailer, err := parser.loadXrefs() if err != nil { - common.Log.Error("Failed to load xref table! %s", err) - // Try to rebuild entire xref table? + common.Log.Debug("ERROR: Failed to load xref table! %s", err) return nil, err } common.Log.Trace("Trailer: %s", trailer) if len(parser.xrefs) == 0 { - return nil, fmt.Errorf("Empty XREF table. Invalid.") + return nil, fmt.Errorf("Empty XREF table - Invalid") } - // printXrefTable(parser.xrefs) - majorVersion, minorVersion, err := parser.parsePdfVersion() if err != nil { common.Log.Error("Unable to parse version: %v", err) @@ -1495,23 +1543,22 @@ func NewParser(rs io.ReadSeeker) (*PdfParser, error) { return parser, nil } -// Check if the document is encrypted. First time when called, will -// check if the Encrypt dictionary is accessible through the trailer -// dictionary. -// If encrypted, prepares a crypt datastructure which can be used to -// authenticate and decrypt the document. -func (this *PdfParser) IsEncrypted() (bool, error) { - if this.crypter != nil { +// IsEncrypted checks if the document is encrypted. A bool flag is returned indicating the result. +// First time when called, will check if the Encrypt dictionary is accessible through the trailer dictionary. +// If encrypted, prepares a crypt datastructure which can be used to authenticate and decrypt the document. +// On failure, an error is returned. +func (parser *PdfParser) IsEncrypted() (bool, error) { + if parser.crypter != nil { return true, nil } - if this.trailer != nil { + if parser.trailer != nil { common.Log.Trace("Checking encryption dictionary!") - encDictRef, isEncrypted := this.trailer.Get("Encrypt").(*PdfObjectReference) + encDictRef, isEncrypted := parser.trailer.Get("Encrypt").(*PdfObjectReference) if isEncrypted { common.Log.Trace("Is encrypted!") common.Log.Trace("0: Look up ref %q", encDictRef) - encObj, err := this.LookupByReference(*encDictRef) + encObj, err := parser.LookupByReference(*encDictRef) common.Log.Trace("1: %q", encObj) if err != nil { return false, err @@ -1528,12 +1575,12 @@ func (this *PdfParser) IsEncrypted() (bool, error) { if !ok { return false, errors.New("Trailer Encrypt object non dictionary") } - crypter, err := PdfCryptMakeNew(this, encDict, this.trailer) + crypter, err := PdfCryptMakeNew(parser, encDict, parser.trailer) if err != nil { return false, err } - this.crypter = &crypter + parser.crypter = &crypter common.Log.Trace("Crypter object %b", crypter) return true, nil } @@ -1541,36 +1588,36 @@ func (this *PdfParser) IsEncrypted() (bool, error) { return false, nil } -// Decrypt the PDF file with a specified password. Also tries to -// decrypt with an empty password. Returns true if successful, -// false otherwise. -func (this *PdfParser) Decrypt(password []byte) (bool, error) { +// Decrypt attempts to decrypt the PDF file with a specified password. Also tries to +// decrypt with an empty password. Returns true if successful, false otherwise. +// An error is returned when there is a problem with decrypting. +func (parser *PdfParser) Decrypt(password []byte) (bool, error) { // Also build the encryption/decryption key. - if this.crypter == nil { + if parser.crypter == nil { return false, errors.New("Check encryption first") } - authenticated, err := this.crypter.authenticate(password) + authenticated, err := parser.crypter.authenticate(password) if err != nil { return false, err } if !authenticated { - authenticated, err = this.crypter.authenticate([]byte("")) + authenticated, err = parser.crypter.authenticate([]byte("")) } return authenticated, err } -// Check access rights and permissions for a specified password. If either user/owner password is specified, -// full rights are granted, otherwise the access rights are specified by the Permissions flag. +// CheckAccessRights checks access rights and permissions for a specified password. If either user/owner password is +// specified, full rights are granted, otherwise the access rights are specified by the Permissions flag. // // The bool flag indicates that the user can access and view the file. // The AccessPermissions shows what access the user has for editing etc. // An error is returned if there was a problem performing the authentication. -func (this *PdfParser) CheckAccessRights(password []byte) (bool, AccessPermissions, error) { +func (parser *PdfParser) CheckAccessRights(password []byte) (bool, AccessPermissions, error) { // Also build the encryption/decryption key. - if this.crypter == nil { + if parser.crypter == nil { // If the crypter is not set, the file is not encrypted and we can assume full access permissions. perms := AccessPermissions{} perms.Printing = true @@ -1584,5 +1631,5 @@ func (this *PdfParser) CheckAccessRights(password []byte) (bool, AccessPermissio return true, perms, nil } - return this.crypter.checkAccessRights(password) + return parser.crypter.checkAccessRights(password) } diff --git a/pdf/core/parser_test.go b/pdf/core/parser_test.go index 5b8f6a3d..c3d47048 100644 --- a/pdf/core/parser_test.go +++ b/pdf/core/parser_test.go @@ -20,11 +20,11 @@ func init() { common.SetLogger(common.ConsoleLogger{}) } -func makeReaderForText(txt string) (*bytes.Reader, *bufio.Reader) { +func makeReaderForText(txt string) (*bytes.Reader, *bufio.Reader, int64) { buf := []byte(txt) bufReader := bytes.NewReader(buf) bufferedReader := bufio.NewReader(bufReader) - return bufReader, bufferedReader + return bufReader, bufferedReader, int64(len(txt)) } func TestNameParsing(t *testing.T) { @@ -47,7 +47,7 @@ func TestNameParsing(t *testing.T) { for str, name := range namePairs { parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(str) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(str) o, err := parser.parseName() if err != nil && err != io.EOF { t.Errorf("Unable to parse name string, error: %s", err) @@ -59,7 +59,7 @@ func TestNameParsing(t *testing.T) { // Should fail (require starting with '/') parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(" /Name") + parser.rs, parser.reader, parser.fileSize = makeReaderForText(" /Name") _, err := parser.parseName() if err == nil || err == io.EOF { t.Errorf("Should be invalid name") @@ -91,13 +91,13 @@ func TestStringParsing(t *testing.T) { } for _, entry := range testEntries { parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(entry.raw) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(entry.raw) o, err := parser.parseString() if err != nil && err != io.EOF { t.Errorf("Unable to parse string, error: %s", err) } if string(o) != entry.expected { - t.Errorf("String Mismatch %s: %q != %q", entry.raw, o, entry.expected) + t.Errorf("String Mismatch %s: \"%s\" != \"%s\"", entry.raw, o, entry.expected) } } } @@ -109,7 +109,7 @@ func TestBinStringParsing(t *testing.T) { "\x8B\x79\x86\x72\x6A\x8C\xDB)" parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(rawText1) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText1) o, err := parser.parseString() if err != nil && err != io.EOF { t.Errorf("Unable to parse string, error: %s", err) @@ -124,7 +124,7 @@ func TestStringParsing2(t *testing.T) { rawText := "[(\\227\\224`\\274\\31W\\216\\276\\23\\231\\246U\\33\\317\\6-)(\\210S\\377:\\322\\278A\\200$*/e]\\371|)]" parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) list, err := parser.parseArray() if err != nil { t.Errorf("Failed to parse string list (%s)", err) @@ -144,14 +144,14 @@ func TestBoolParsing(t *testing.T) { for key, expected := range testEntries { parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(key) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(key) val, err := parser.parseBool() if err != nil { t.Errorf("Error parsing bool: %s", err) return } if bool(val) != expected { - t.Errorf("bool not as expected (%t)", val) + t.Errorf("bool not as expected (got %t, expected %t)", bool(val), expected) return } } @@ -161,7 +161,7 @@ func TestNumericParsing1(t *testing.T) { // 7.3.3 txt1 := "[34.5 -3.62 1 +123.6 4. -.002 0.0]" parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(txt1) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(txt1) list, err := parser.parseArray() if err != nil { t.Errorf("Error parsing array") @@ -188,7 +188,7 @@ func TestNumericParsing1(t *testing.T) { return } if float32(*num) != val { - t.Errorf("Idx %d, value incorrect (%f)", idx) + t.Errorf("Idx %d, value incorrect (%f)", idx, val) } } @@ -207,7 +207,7 @@ func TestNumericParsing2(t *testing.T) { // 7.3.3 txt1 := "[+4.-.002]" // 4.0 and -0.002 parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(txt1) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(txt1) list, err := parser.parseArray() if err != nil { t.Errorf("Error parsing array") @@ -230,7 +230,7 @@ func TestNumericParsing2(t *testing.T) { return } if float32(*num) != val { - t.Errorf("Idx %d, value incorrect (%f)", idx) + t.Errorf("Idx %d, value incorrect (%f)", idx, val) } } } @@ -240,7 +240,7 @@ func TestNumericParsing3(t *testing.T) { // 7.3.3 txt1 := "[+4.-.002+3e-2-2e0]" // 4.0, -0.002, 1e-2, -2.0 parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(txt1) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(txt1) list, err := parser.parseArray() if err != nil { t.Errorf("Error parsing array (%s)", err) @@ -265,7 +265,7 @@ func TestNumericParsing3(t *testing.T) { return } if float32(*num) != val { - t.Errorf("Idx %d, value incorrect (%f)", idx) + t.Errorf("Idx %d, value incorrect (%f)", idx, val) } } } @@ -282,7 +282,7 @@ func TestHexStringParsing(t *testing.T) { func TestDictParsing1(t *testing.T) { txt1 := "<<\n\t/Name /Game /key/val/data\t[0 1 2 3.14 5]\t\n\n>>" parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(txt1) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(txt1) dict, err := parser.ParseDict() if err != nil { t.Errorf("Error parsing dict") @@ -326,7 +326,7 @@ func TestDictParsing2(t *testing.T) { ">>\n >>" parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) dict, err := parser.ParseDict() if err != nil { t.Errorf("Error parsing dict") @@ -364,7 +364,7 @@ func TestDictParsing3(t *testing.T) { rawText := "<<>>" parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) dict, err := parser.ParseDict() if err != nil { t.Errorf("Error parsing dict") @@ -441,7 +441,7 @@ endobj 3 0 obj ` parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) obj, err := parser.ParseIndirectObject() if err != nil { @@ -475,7 +475,7 @@ endobj` parser := PdfParser{} parser.xrefs = make(XrefTable) parser.objstms = make(ObjectStreams) - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) xrefDict, err := parser.parseXrefStream(nil) if err != nil { @@ -516,7 +516,7 @@ func TestObjectParse(t *testing.T) { // Test object detection. // Invalid object type. rawText := " \t9 0 false" - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) obj, err := parser.parseObject() if err != nil { t.Error("Should ignore tab/space") @@ -525,7 +525,7 @@ func TestObjectParse(t *testing.T) { // Integer rawText = "9 0 false" - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) obj, err = parser.parseObject() if err != nil { @@ -544,7 +544,7 @@ func TestObjectParse(t *testing.T) { // Reference rawText = "9 0 R false" - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) obj, err = parser.parseObject() if err != nil { t.Errorf("Error parsing object") @@ -562,7 +562,7 @@ func TestObjectParse(t *testing.T) { // Reference rawText = "909 0 R false" - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) obj, err = parser.parseObject() if err != nil { t.Errorf("Error parsing object") @@ -580,7 +580,7 @@ func TestObjectParse(t *testing.T) { // Bool rawText = "false 9 0 R" - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) obj, err = parser.parseObject() if err != nil { t.Errorf("Error parsing object") diff --git a/pdf/core/primitives.go b/pdf/core/primitives.go index 0f7b5f81..461f2fbe 100644 --- a/pdf/core/primitives.go +++ b/pdf/core/primitives.go @@ -3,11 +3,6 @@ * file 'LICENSE.md', which is part of this source code package. */ -// Defines PDF primitive objects as per the standard. Also defines a PdfObject -// interface allowing to universally work with these objects. It allows -// recursive writing of the objects to file as well and stringifying for -// debug purposes. - package core import ( @@ -17,41 +12,63 @@ import ( "github.com/unidoc/unidoc/common" ) -// PDF Primitives implement the PdfObject interface. +// PdfObject is an interface which all primitive PDF objects must implement. type PdfObject interface { - String() string // Output a string representation of the primitive (for debugging). - DefaultWriteString() string // Output the PDF primitive as expected by the standard. + // Output a string representation of the primitive (for debugging). + String() string + + // Output the PDF primitive as written to file as expected by the standard. + DefaultWriteString() string } +// PdfObjectBool represents the primitive PDF boolean object. type PdfObjectBool bool + +// PdfObjectInteger represents the primitive PDF integer numerical object. type PdfObjectInteger int64 + +// PdfObjectFloat represents the primitive PDF floating point numerical object. type PdfObjectFloat float64 + +// PdfObjectString represents the primitive PDF string object. +// TODO (v3): Change to a struct and add a flag for hex/plaintext. type PdfObjectString string + +// PdfObjectName represents the primitive PDF name object. type PdfObjectName string + +// PdfObjectArray represents the primitive PDF array object. type PdfObjectArray []PdfObject + +// PdfObjectDictionary represents the primitive PDF dictionary/map object. type PdfObjectDictionary struct { dict map[PdfObjectName]PdfObject keys []PdfObjectName } + +// PdfObjectNull represents the primitive PDF null object. type PdfObjectNull struct{} +// PdfObjectReference represents the primitive PDF reference object. type PdfObjectReference struct { ObjectNumber int64 GenerationNumber int64 } +// PdfIndirectObject represents the primitive PDF indirect object. type PdfIndirectObject struct { PdfObjectReference PdfObject } +// PdfObjectStream represents the primitive PDF Object stream. type PdfObjectStream struct { PdfObjectReference *PdfObjectDictionary Stream []byte } -// Quick functions to make pdf objects form primitive objects. +// MakeDict creates and returns an empty PdfObjectDictionary. func MakeDict() *PdfObjectDictionary { d := &PdfObjectDictionary{} d.dict = map[PdfObjectName]PdfObject{} @@ -59,16 +76,19 @@ func MakeDict() *PdfObjectDictionary { return d } +// MakeName creates a PdfObjectName from a string. func MakeName(s string) *PdfObjectName { name := PdfObjectName(s) return &name } +// MakeInteger creates a PdfObjectInteger from an int64. func MakeInteger(val int64) *PdfObjectInteger { num := PdfObjectInteger(val) return &num } +// MakeArray creates an PdfObjectArray from a list of PdfObjects. func MakeArray(objects ...PdfObject) *PdfObjectArray { array := PdfObjectArray{} for _, obj := range objects { @@ -77,6 +97,8 @@ func MakeArray(objects ...PdfObject) *PdfObjectArray { return &array } +// MakeArrayFromIntegers creates an PdfObjectArray from a slice of ints, where each array element is +// an PdfObjectInteger. func MakeArrayFromIntegers(vals []int) *PdfObjectArray { array := PdfObjectArray{} for _, val := range vals { @@ -85,6 +107,8 @@ func MakeArrayFromIntegers(vals []int) *PdfObjectArray { return &array } +// MakeArrayFromIntegers64 creates an PdfObjectArray from a slice of int64s, where each array element +// is an PdfObjectInteger. func MakeArrayFromIntegers64(vals []int64) *PdfObjectArray { array := PdfObjectArray{} for _, val := range vals { @@ -93,6 +117,8 @@ func MakeArrayFromIntegers64(vals []int64) *PdfObjectArray { return &array } +// MakeArrayFromFloats creates an PdfObjectArray from a slice of float64s, where each array element is an +// PdfObjectFloat. func MakeArrayFromFloats(vals []float64) *PdfObjectArray { array := PdfObjectArray{} for _, val := range vals { @@ -101,27 +127,33 @@ func MakeArrayFromFloats(vals []float64) *PdfObjectArray { return &array } +// MakeFloat creates an PdfObjectFloat from a float64. func MakeFloat(val float64) *PdfObjectFloat { num := PdfObjectFloat(val) return &num } +// MakeString creates an PdfObjectString from a string. func MakeString(s string) *PdfObjectString { str := PdfObjectString(s) return &str } +// MakeNull creates an PdfObjectNull. func MakeNull() *PdfObjectNull { null := PdfObjectNull{} return &null } +// MakeIndirectObject creates an PdfIndirectObject with a specified direct object PdfObject. func MakeIndirectObject(obj PdfObject) *PdfIndirectObject { ind := &PdfIndirectObject{} ind.PdfObject = obj return ind } +// MakeStream creates an PdfObjectStream with specified contents and encoding. If encoding is nil, then raw encoding +// will be used (i.e. no encoding applied). func MakeStream(contents []byte, encoder StreamEncoder) (*PdfObjectStream, error) { stream := &PdfObjectStream{} @@ -141,44 +173,47 @@ func MakeStream(contents []byte, encoder StreamEncoder) (*PdfObjectStream, error return stream, nil } -func (this *PdfObjectBool) String() string { - if *this { +func (bool *PdfObjectBool) String() string { + if *bool { return "true" } else { return "false" } } -func (this *PdfObjectBool) DefaultWriteString() string { - if *this { +// DefaultWriteString outputs the object as it is to be written to file. +func (bool *PdfObjectBool) DefaultWriteString() string { + if *bool { return "true" } else { return "false" } } -func (this *PdfObjectInteger) String() string { - return fmt.Sprintf("%d", *this) +func (int *PdfObjectInteger) String() string { + return fmt.Sprintf("%d", *int) } -func (this *PdfObjectInteger) DefaultWriteString() string { - return fmt.Sprintf("%d", *this) +// DefaultWriteString outputs the object as it is to be written to file. +func (int *PdfObjectInteger) DefaultWriteString() string { + return fmt.Sprintf("%d", *int) } -func (this *PdfObjectFloat) String() string { - return fmt.Sprintf("%f", *this) +func (float *PdfObjectFloat) String() string { + return fmt.Sprintf("%f", *float) } -func (this *PdfObjectFloat) DefaultWriteString() string { - return fmt.Sprintf("%f", *this) - +// DefaultWriteString outputs the object as it is to be written to file. +func (float *PdfObjectFloat) DefaultWriteString() string { + return fmt.Sprintf("%f", *float) } -func (this *PdfObjectString) String() string { - return fmt.Sprintf("%s", string(*this)) +func (str *PdfObjectString) String() string { + return fmt.Sprintf("%s", string(*str)) } -func (this *PdfObjectString) DefaultWriteString() string { +// DefaultWriteString outputs the object as it is to be written to file. +func (str *PdfObjectString) DefaultWriteString() string { var output bytes.Buffer escapeSequences := map[byte]string{ @@ -193,8 +228,8 @@ func (this *PdfObjectString) DefaultWriteString() string { } output.WriteString("(") - for i := 0; i < len(*this); i++ { - char := (*this)[i] + for i := 0; i < len(*str); i++ { + char := (*str)[i] if escStr, useEsc := escapeSequences[char]; useEsc { output.WriteString(escStr) } else { @@ -206,20 +241,21 @@ func (this *PdfObjectString) DefaultWriteString() string { return output.String() } -func (this *PdfObjectName) String() string { - return fmt.Sprintf("%s", string(*this)) +func (name *PdfObjectName) String() string { + return fmt.Sprintf("%s", string(*name)) } -func (this *PdfObjectName) DefaultWriteString() string { +// DefaultWriteString outputs the object as it is to be written to file. +func (name *PdfObjectName) DefaultWriteString() string { var output bytes.Buffer - if len(*this) > 127 { - common.Log.Error("Name too long (%s)", *this) + if len(*name) > 127 { + common.Log.Debug("ERROR: Name too long (%s)", *name) } output.WriteString("/") - for i := 0; i < len(*this); i++ { - char := (*this)[i] + for i := 0; i < len(*name); i++ { + char := (*name)[i] if !IsPrintable(char) || char == '#' || IsDelimiter(char) { output.WriteString(fmt.Sprintf("#%.2x", char)) } else { @@ -230,10 +266,12 @@ func (this *PdfObjectName) DefaultWriteString() string { return output.String() } -func (this *PdfObjectArray) ToFloat64Array() ([]float64, error) { +// ToFloat64Array returns a slice of all elements in the array as a float64 slice. An error is returned if the array +// contains non-numeric objects (each element can be either PdfObjectInteger or PdfObjectFloat). +func (array *PdfObjectArray) ToFloat64Array() ([]float64, error) { vals := []float64{} - for _, obj := range *this { + for _, obj := range *array { if number, is := obj.(*PdfObjectInteger); is { vals = append(vals, float64(*number)) } else if number, is := obj.(*PdfObjectFloat); is { @@ -246,10 +284,12 @@ func (this *PdfObjectArray) ToFloat64Array() ([]float64, error) { return vals, nil } -func (this *PdfObjectArray) ToIntegerArray() ([]int, error) { +// ToIntegerArray returns a slice of all array elements as an int slice. An error is returned if the array contains +// non-integer objects. Each element can only be PdfObjectInteger. +func (array *PdfObjectArray) ToIntegerArray() ([]int, error) { vals := []int{} - for _, obj := range *this { + for _, obj := range *array { if number, is := obj.(*PdfObjectInteger); is { vals = append(vals, int(*number)) } else { @@ -260,11 +300,11 @@ func (this *PdfObjectArray) ToIntegerArray() ([]int, error) { return vals, nil } -func (this *PdfObjectArray) String() string { +func (array *PdfObjectArray) String() string { outStr := "[" - for ind, o := range *this { + for ind, o := range *array { outStr += o.String() - if ind < (len(*this) - 1) { + if ind < (len(*array) - 1) { outStr += ", " } } @@ -272,11 +312,12 @@ func (this *PdfObjectArray) String() string { return outStr } -func (this *PdfObjectArray) DefaultWriteString() string { +// DefaultWriteString outputs the object as it is to be written to file. +func (array *PdfObjectArray) DefaultWriteString() string { outStr := "[" - for ind, o := range *this { + for ind, o := range *array { outStr += o.DefaultWriteString() - if ind < (len(*this) - 1) { + if ind < (len(*array) - 1) { outStr += " " } } @@ -284,8 +325,9 @@ func (this *PdfObjectArray) DefaultWriteString() string { return outStr } -func (this *PdfObjectArray) Append(obj PdfObject) { - *this = append(*this, obj) +// Append adds an PdfObject to the array. +func (array *PdfObjectArray) Append(obj PdfObject) { + *array = append(*array, obj) } func getNumberAsFloat(obj PdfObject) (float64, error) { @@ -300,12 +342,12 @@ func getNumberAsFloat(obj PdfObject) (float64, error) { return 0, fmt.Errorf("Not a number") } -// For numeric array: Get the array in []float64 slice representation. -// Will return error if not entirely numeric. -func (this *PdfObjectArray) GetAsFloat64Slice() ([]float64, error) { +// GetAsFloat64Slice returns the array as []float64 slice. +// Returns an error if not entirely numeric (only PdfObjectIntegers, PdfObjectFloats). +func (array *PdfObjectArray) GetAsFloat64Slice() ([]float64, error) { slice := []float64{} - for _, obj := range *this { + for _, obj := range *array { obj := TraceToDirectObject(obj) number, err := getNumberAsFloat(obj) if err != nil { @@ -317,30 +359,31 @@ func (this *PdfObjectArray) GetAsFloat64Slice() ([]float64, error) { return slice, nil } -// Merge in key/values from another dictionary. Overwriting if has same keys. -func (this *PdfObjectDictionary) Merge(another *PdfObjectDictionary) { +// Merge merges in key/values from another dictionary. Overwriting if has same keys. +func (d *PdfObjectDictionary) Merge(another *PdfObjectDictionary) { if another != nil { for _, key := range another.Keys() { val := another.Get(key) - this.Set(key, val) + d.Set(key, val) } } } -func (this *PdfObjectDictionary) String() string { +func (d *PdfObjectDictionary) String() string { outStr := "Dict(" - for _, k := range this.keys { - v := this.dict[k] + for _, k := range d.keys { + v := d.dict[k] outStr += fmt.Sprintf("\"%s\": %s, ", k, v.String()) } outStr += ")" return outStr } -func (this *PdfObjectDictionary) DefaultWriteString() string { +// DefaultWriteString outputs the object as it is to be written to file. +func (d *PdfObjectDictionary) DefaultWriteString() string { outStr := "<<" - for _, k := range this.keys { - v := this.dict[k] + for _, k := range d.keys { + v := d.dict[k] common.Log.Trace("Writing k: %s %T %v %v", k, v, k, v) outStr += k.DefaultWriteString() outStr += " " @@ -350,6 +393,7 @@ func (this *PdfObjectDictionary) DefaultWriteString() string { return outStr } +// Set sets the dictionary's key -> val mapping entry. Overwrites if key already set. func (d *PdfObjectDictionary) Set(key PdfObjectName, val PdfObject) { found := false for _, k := range d.keys { @@ -366,7 +410,7 @@ func (d *PdfObjectDictionary) Set(key PdfObjectName, val PdfObject) { d.dict[key] = val } -// Get PdfObject corresponding to the specified key. +// Get returns the PdfObject corresponding to the specified key. // Returns a nil value if the key is not set. // // The design is such that we only return 1 value. @@ -381,12 +425,12 @@ func (d *PdfObjectDictionary) Get(key PdfObjectName) PdfObject { return val } -// Get the list of keys. +// Keys returns the list of keys in the dictionary. func (d *PdfObjectDictionary) Keys() []PdfObjectName { return d.keys } -// Remove an element specified by key. +// Remove removes an element specified by key. func (d *PdfObjectDictionary) Remove(key PdfObjectName) { idx := -1 for i, k := range d.keys { @@ -403,9 +447,7 @@ func (d *PdfObjectDictionary) Remove(key PdfObjectName) { } } -// Check if the value's PdfObject interface, or its containing value is nil. Only set the -// key/value pair if not nil. -// +// SetIfNotNil sets the dictionary's key -> val mapping entry -IF- val is not nil. // Note that we take care to perform a type switch. Otherwise if we would supply a nil value // of another type, e.g. (PdfObjectArray*)(nil), then it would not be a PdfObject(nil) and thus // would get set. @@ -463,50 +505,55 @@ func (d *PdfObjectDictionary) SetIfNotNil(key PdfObjectName, val PdfObject) { } } -func (this *PdfObjectReference) String() string { - return fmt.Sprintf("Ref(%d %d)", this.ObjectNumber, this.GenerationNumber) +func (ref *PdfObjectReference) String() string { + return fmt.Sprintf("Ref(%d %d)", ref.ObjectNumber, ref.GenerationNumber) } -func (this *PdfObjectReference) DefaultWriteString() string { - return fmt.Sprintf("%d %d R", this.ObjectNumber, this.GenerationNumber) +// DefaultWriteString outputs the object as it is to be written to file. +func (ref *PdfObjectReference) DefaultWriteString() string { + return fmt.Sprintf("%d %d R", ref.ObjectNumber, ref.GenerationNumber) } -func (this *PdfIndirectObject) String() string { +func (ind *PdfIndirectObject) String() string { // Avoid printing out the object, can cause problems with circular // references. - return fmt.Sprintf("IObject:%d", (*this).ObjectNumber) + return fmt.Sprintf("IObject:%d", (*ind).ObjectNumber) } -func (this *PdfIndirectObject) DefaultWriteString() string { - outStr := fmt.Sprintf("%d 0 R", (*this).ObjectNumber) +// DefaultWriteString outputs the object as it is to be written to file. +func (ind *PdfIndirectObject) DefaultWriteString() string { + outStr := fmt.Sprintf("%d 0 R", (*ind).ObjectNumber) return outStr } -func (this *PdfObjectStream) String() string { - return fmt.Sprintf("Object stream %d: %s", this.ObjectNumber, this.PdfObjectDictionary) +func (stream *PdfObjectStream) String() string { + return fmt.Sprintf("Object stream %d: %s", stream.ObjectNumber, stream.PdfObjectDictionary) } -func (this *PdfObjectStream) DefaultWriteString() string { - outStr := fmt.Sprintf("%d 0 R", (*this).ObjectNumber) +// DefaultWriteString outputs the object as it is to be written to file. +func (stream *PdfObjectStream) DefaultWriteString() string { + outStr := fmt.Sprintf("%d 0 R", (*stream).ObjectNumber) return outStr } -func (this *PdfObjectNull) String() string { +func (null *PdfObjectNull) String() string { return "null" } -func (this *PdfObjectNull) DefaultWriteString() string { +// DefaultWriteString outputs the object as it is to be written to file. +func (null *PdfObjectNull) DefaultWriteString() string { return "null" } // Handy functions to work with primitive objects. -// Traces a pdf object to a direct object. For example contained -// in indirect objects (can be double referenced even). -// -// Note: This function does not trace/resolve references. -// That needs to be done beforehand. + +// TraceMaxDepth specifies the maximum recursion depth allowed. const TraceMaxDepth = 20 +// TraceToDirectObject traces a PdfObject to a direct object. For example direct objects contained +// in indirect objects (can be double referenced even). +// +// Note: This function does not trace/resolve references. That needs to be done beforehand. func TraceToDirectObject(obj PdfObject) PdfObject { iobj, isIndirectObj := obj.(*PdfIndirectObject) depth := 0 diff --git a/pdf/core/repairs.go b/pdf/core/repairs.go index 94be6dcb..2b772d1e 100644 --- a/pdf/core/repairs.go +++ b/pdf/core/repairs.go @@ -24,16 +24,16 @@ var repairReXrefTable = regexp.MustCompile(`[\r\n]\s*(xref)\s*[\r\n]`) // Locates a standard Xref table by looking for the "xref" entry. // Xref object stream not supported. -func (this *PdfParser) repairLocateXref() (int64, error) { +func (parser *PdfParser) repairLocateXref() (int64, error) { readBuf := int64(1000) - this.rs.Seek(-readBuf, os.SEEK_CUR) + parser.rs.Seek(-readBuf, os.SEEK_CUR) - curOffset, err := this.rs.Seek(0, os.SEEK_CUR) + curOffset, err := parser.rs.Seek(0, os.SEEK_CUR) if err != nil { return 0, err } b2 := make([]byte, readBuf) - this.rs.Read(b2) + parser.rs.Read(b2) results := repairReXrefTable.FindAllStringIndex(string(b2), -1) if len(results) < 1 { @@ -49,19 +49,19 @@ func (this *PdfParser) repairLocateXref() (int64, error) { // Renumbers the xref table. // Useful when the cross reference is pointing to an object with the wrong number. // Update the table. -func (this *PdfParser) rebuildXrefTable() error { +func (parser *PdfParser) rebuildXrefTable() error { newXrefs := XrefTable{} - for objNum, xref := range this.xrefs { - obj, _, err := this.lookupByNumberWrapper(objNum, false) + for objNum, xref := range parser.xrefs { + obj, _, err := parser.lookupByNumberWrapper(objNum, false) if err != nil { common.Log.Debug("ERROR: Unable to look up object (%s)", err) common.Log.Debug("ERROR: Xref table completely broken - attempting to repair ") - xrefTable, err := this.repairRebuildXrefsTopDown() + xrefTable, err := parser.repairRebuildXrefsTopDown() if err != nil { common.Log.Debug("ERROR: Failed xref rebuild repair (%s)", err) return err } - this.xrefs = *xrefTable + parser.xrefs = *xrefTable common.Log.Debug("Repaired xref table built") return nil } @@ -75,9 +75,9 @@ func (this *PdfParser) rebuildXrefTable() error { newXrefs[int(actObjNum)] = xref } - this.xrefs = newXrefs + parser.xrefs = newXrefs common.Log.Debug("New xref table built") - printXrefTable(this.xrefs) + printXrefTable(parser.xrefs) return nil } @@ -97,16 +97,16 @@ func parseObjectNumberFromString(str string) (int, int, error) { // Parse the entire file from top down. // Goes through the file byte-by-byte looking for " obj" patterns. // N.B. This collects the XREF_TABLE_ENTRY data only. -func (this *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) { - if this.repairsAttempted { +func (parser *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) { + if parser.repairsAttempted { // Avoid multiple repairs (only try once). return nil, fmt.Errorf("Repair failed") } - this.repairsAttempted = true + parser.repairsAttempted = true // Go to beginning, reset reader. - this.rs.Seek(0, os.SEEK_SET) - this.reader = bufio.NewReader(this.rs) + parser.rs.Seek(0, os.SEEK_SET) + parser.reader = bufio.NewReader(parser.rs) // Keep a running buffer of last bytes. bufLen := 20 @@ -114,7 +114,7 @@ func (this *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) { xrefTable := XrefTable{} for { - b, err := this.reader.ReadByte() + b, err := parser.reader.ReadByte() if err != nil { if err == io.EOF { break @@ -157,7 +157,7 @@ func (this *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) { continue // Probably too long to be a valid object... } - objOffset := this.GetFileOffset() - int64(bufLen-i) + objOffset := parser.GetFileOffset() - int64(bufLen-i) objstr := append(last[i+1:], b) objNum, genNum, err := parseObjectNumberFromString(string(objstr)) @@ -185,9 +185,9 @@ func (this *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) { } // Look for first sign of xref table from end of file. -func (this *PdfParser) repairSeekXrefMarker() error { +func (parser *PdfParser) repairSeekXrefMarker() error { // Get the file size. - fSize, err := this.rs.Seek(0, os.SEEK_END) + fSize, err := parser.rs.Seek(0, os.SEEK_END) if err != nil { return err } @@ -206,14 +206,14 @@ func (this *PdfParser) repairSeekXrefMarker() error { } // Move back enough (as we need to read forward). - _, err := this.rs.Seek(-offset-buflen, os.SEEK_END) + _, err := parser.rs.Seek(-offset-buflen, os.SEEK_END) if err != nil { return err } // Read the data. b1 := make([]byte, buflen) - this.rs.Read(b1) + parser.rs.Read(b1) common.Log.Trace("Looking for xref : \"%s\"", string(b1)) ind := reXrefTableStart.FindAllStringIndex(string(b1), -1) @@ -221,11 +221,11 @@ func (this *PdfParser) repairSeekXrefMarker() error { // Found it. lastInd := ind[len(ind)-1] common.Log.Trace("Ind: % d", ind) - this.rs.Seek(-offset-buflen+int64(lastInd[0]), os.SEEK_END) - this.reader = bufio.NewReader(this.rs) + parser.rs.Seek(-offset-buflen+int64(lastInd[0]), os.SEEK_END) + parser.reader = bufio.NewReader(parser.rs) // Go past whitespace, finish at 'x'. for { - bb, err := this.reader.Peek(1) + bb, err := parser.reader.Peek(1) if err != nil { return err } @@ -233,7 +233,7 @@ func (this *PdfParser) repairSeekXrefMarker() error { if !IsWhiteSpace(bb[0]) { break } - this.reader.Discard(1) + parser.reader.Discard(1) } return nil @@ -250,17 +250,17 @@ func (this *PdfParser) repairSeekXrefMarker() error { // Called when Pdf version not found normally. Looks for the PDF version by scanning top-down. // %PDF-1.7 -func (this *PdfParser) seekPdfVersionTopDown() (int, int, error) { +func (parser *PdfParser) seekPdfVersionTopDown() (int, int, error) { // Go to beginning, reset reader. - this.rs.Seek(0, os.SEEK_SET) - this.reader = bufio.NewReader(this.rs) + parser.rs.Seek(0, os.SEEK_SET) + parser.reader = bufio.NewReader(parser.rs) // Keep a running buffer of last bytes. bufLen := 20 last := make([]byte, bufLen) for { - b, err := this.reader.ReadByte() + b, err := parser.reader.ReadByte() if err != nil { if err == io.EOF { break diff --git a/pdf/core/stream.go b/pdf/core/stream.go index fea5e667..c806779d 100644 --- a/pdf/core/stream.go +++ b/pdf/core/stream.go @@ -11,9 +11,9 @@ import ( "github.com/unidoc/unidoc/common" ) -// Creates the encoder from the stream's dictionary. +// NewEncoderFromStream creates a StreamEncoder based on the stream's dictionary. func NewEncoderFromStream(streamObj *PdfObjectStream) (StreamEncoder, error) { - filterObj := streamObj.PdfObjectDictionary.Get("Filter") + filterObj := TraceToDirectObject(streamObj.PdfObjectDictionary.Get("Filter")) if filterObj == nil { // No filter, return raw data back. return NewRawEncoder(), nil @@ -61,18 +61,26 @@ func NewEncoderFromStream(streamObj *PdfObjectStream) (StreamEncoder, error) { return newLZWEncoderFromStream(streamObj, nil) } else if *method == StreamEncodingFilterNameDCT { return newDCTEncoderFromStream(streamObj, nil) + } else if *method == StreamEncodingFilterNameRunLength { + return newRunLengthEncoderFromStream(streamObj, nil) } else if *method == StreamEncodingFilterNameASCIIHex { return NewASCIIHexEncoder(), nil } else if *method == StreamEncodingFilterNameASCII85 { return NewASCII85Encoder(), nil + } else if *method == StreamEncodingFilterNameCCITTFax { + return NewCCITTFaxEncoder(), nil + } else if *method == StreamEncodingFilterNameJBIG2 { + return NewJBIG2Encoder(), nil + } else if *method == StreamEncodingFilterNameJPX { + return NewJPXEncoder(), nil } else { common.Log.Debug("ERROR: Unsupported encoding method!") return nil, fmt.Errorf("Unsupported encoding method (%s)", *method) } } -// Decodes the stream. -// Supports FlateDecode, ASCIIHexDecode, LZW. +// DecodeStream decodes the stream data and returns the decoded data. +// An error is returned upon failure. func DecodeStream(streamObj *PdfObjectStream) ([]byte, error) { common.Log.Trace("Decode stream") @@ -92,8 +100,7 @@ func DecodeStream(streamObj *PdfObjectStream) ([]byte, error) { return decoded, nil } -// Encodes the stream. -// Uses the encoding specified by the object. +// EncodeStream encodes the stream data using the encoded specified by the stream's dictionary. func EncodeStream(streamObj *PdfObjectStream) error { common.Log.Trace("Encode stream") diff --git a/pdf/core/stream_test.go b/pdf/core/stream_test.go index 5f942760..925e7cb9 100644 --- a/pdf/core/stream_test.go +++ b/pdf/core/stream_test.go @@ -77,7 +77,7 @@ stream endobj` parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) obj, err := parser.ParseIndirectObject() if err != nil { @@ -136,7 +136,7 @@ stream endobj` parser := PdfParser{} - parser.rs, parser.reader = makeReaderForText(rawText) + parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText) obj, err := parser.ParseIndirectObject() if err != nil { diff --git a/pdf/core/symbols.go b/pdf/core/symbols.go index d664eecc..e076a7a9 100644 --- a/pdf/core/symbols.go +++ b/pdf/core/symbols.go @@ -5,6 +5,8 @@ package core +// IsWhiteSpace checks if byte represents a white space character. +// TODO (v3): Unexport. func IsWhiteSpace(ch byte) bool { // Table 1 white-space characters (7.2.2 Character Set) // spaceCharacters := string([]byte{0x00, 0x09, 0x0A, 0x0C, 0x0D, 0x20}) @@ -15,10 +17,14 @@ func IsWhiteSpace(ch byte) bool { } } +// IsFloatDigit checks if a character can be a part of a float number string. +// TODO (v3): Unexport. func IsFloatDigit(c byte) bool { return ('0' <= c && c <= '9') || c == '.' } +// IsDecimalDigit checks if the character is a part of a decimal number string. +// TODO (v3): Unexport. func IsDecimalDigit(c byte) bool { if c >= '0' && c <= '9' { return true @@ -27,6 +33,8 @@ func IsDecimalDigit(c byte) bool { } } +// IsOctalDigit checks if a character can be part of an octal digit string. +// TODO (v3): Unexport. func IsOctalDigit(c byte) bool { if c >= '0' && c <= '7' { return true @@ -35,8 +43,10 @@ func IsOctalDigit(c byte) bool { } } +// IsPrintable checks if a character is printable. // Regular characters that are outside the range EXCLAMATION MARK(21h) // (!) to TILDE (7Eh) (~) should be written using the hexadecimal notation. +// TODO (v3): Unexport. func IsPrintable(char byte) bool { if char < 0x21 || char > 0x7E { return false @@ -44,6 +54,8 @@ func IsPrintable(char byte) bool { return true } +// IsDelimiter checks if a character represents a delimiter. +// TODO (v3): Unexport. func IsDelimiter(char byte) bool { if char == '(' || char == ')' { return true diff --git a/pdf/core/utils.go b/pdf/core/utils.go index 437bebff..10f0f30f 100644 --- a/pdf/core/utils.go +++ b/pdf/core/utils.go @@ -6,15 +6,46 @@ package core import ( + "errors" "fmt" "sort" "github.com/unidoc/unidoc/common" ) +// Check slice range to make sure within bounds for accessing: +// slice[a:b] where sliceLen=len(slice). +func checkBounds(sliceLen, a, b int) error { + if a < 0 || a > sliceLen { + return errors.New("Slice index a out of bounds") + } + if b < a { + return errors.New("Invalid slice index b < a") + } + if b > sliceLen { + return errors.New("Slice index b out of bounds") + } + + return nil +} + // Inspect analyzes the document object structure. -func (this *PdfParser) Inspect() (map[string]int, error) { - return this.inspect() +func (parser *PdfParser) Inspect() (map[string]int, error) { + return parser.inspect() +} + +// GetObjectNums returns a sorted list of object numbers of the PDF objects in the file. +func (parser *PdfParser) GetObjectNums() []int { + objNums := []int{} + for _, x := range parser.xrefs { + objNums = append(objNums, x.objectNumber) + } + + // Sort the object numbers to give consistent ordering of PDF objects in output. + // Needed since parser.xrefs is a map. + sort.Ints(objNums) + + return objNums } func getUniDocVersion() string { @@ -26,7 +57,7 @@ func getUniDocVersion() string { * Go through all objects in the cross ref table and detect the types. * Mostly for debugging purposes and inspecting odd PDF files. */ -func (this *PdfParser) inspect() (map[string]int, error) { +func (parser *PdfParser) inspect() (map[string]int, error) { common.Log.Trace("--------INSPECT ----------") common.Log.Trace("Xref table:") @@ -35,21 +66,21 @@ func (this *PdfParser) inspect() (map[string]int, error) { failedCount := 0 keys := []int{} - for k, _ := range this.xrefs { + for k := range parser.xrefs { keys = append(keys, k) } sort.Ints(keys) i := 0 for _, k := range keys { - xref := this.xrefs[k] + xref := parser.xrefs[k] if xref.objectNumber == 0 { continue } objCount++ common.Log.Trace("==========") common.Log.Trace("Looking up object number: %d", xref.objectNumber) - o, err := this.LookupByNumber(xref.objectNumber) + o, err := parser.LookupByNumber(xref.objectNumber) if err != nil { common.Log.Trace("ERROR: Fail to lookup obj %d (%s)", xref.objectNumber, err) failedCount++ @@ -129,8 +160,8 @@ func (this *PdfParser) inspect() (map[string]int, error) { } common.Log.Trace("=======") - if len(this.xrefs) < 1 { - common.Log.Error("This document is invalid (xref table missing!)") + if len(parser.xrefs) < 1 { + common.Log.Debug("ERROR: This document is invalid (xref table missing!)") return nil, fmt.Errorf("Invalid document (xref table missing)") } diff --git a/pdf/creator/block.go b/pdf/creator/block.go index 34fe5ede..029661fb 100644 --- a/pdf/creator/block.go +++ b/pdf/creator/block.go @@ -15,7 +15,7 @@ import ( "github.com/unidoc/unidoc/pdf/model" ) -// A block can contain a portion of PDF Page contents. It has a width and a position and can +// Block contains a portion of PDF Page contents. It has a width and a position and can // be placed anywhere on a Page. It can even contain a whole Page, and is used in the creator // where each Drawable object can output one or more blocks, each representing content for separate pages // (typically needed when Page breaks occur). @@ -41,7 +41,7 @@ type Block struct { margins margins } -// Create a new block with specified width and height. +// NewBlock creates a new Block with specified width and height. func NewBlock(width float64, height float64) *Block { b := &Block{} b.contents = &contentstream.ContentStreamOperations{} @@ -51,8 +51,8 @@ func NewBlock(width float64, height float64) *Block { return b } -// Create a block from a PDF Page. Useful for loading template pages as blocks from a PDF document and additional -// content with the creator. +// NewBlockFromPage creates a Block from a PDF Page. Useful for loading template pages as blocks from a PDF document +// and additional content with the creator. func NewBlockFromPage(page *model.PdfPage) (*Block, error) { b := &Block{} @@ -91,7 +91,7 @@ func NewBlockFromPage(page *model.PdfPage) (*Block, error) { return b, nil } -// Set the rotation angle in degrees. +// SetAngle sets the rotation angle in degrees. func (blk *Block) SetAngle(angleDeg float64) { blk.angle = angleDeg } @@ -112,7 +112,8 @@ func (blk *Block) duplicate() *Block { return dup } -// Draws the block contents on a template Page block. +// GeneratePageBlocks draws the block contents on a template Page block. +// Implements the Drawable interface. func (blk *Block) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { blocks := []*Block{} @@ -156,12 +157,12 @@ func (blk *Block) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, er return blocks, ctx, nil } -// Get block height. +// Height returns the Block's height. func (blk *Block) Height() float64 { return blk.height } -// Get block width. +// Width returns the Block's width. func (blk *Block) Width() float64 { return blk.width } @@ -189,7 +190,7 @@ func (blk *Block) addContentsByString(contents string) error { return nil } -// Set block Margins. +// SetMargins sets the Block's left, right, top, bottom, margins. func (blk *Block) SetMargins(left, right, top, bottom float64) { blk.margins.left = left blk.margins.right = right @@ -197,12 +198,12 @@ func (blk *Block) SetMargins(left, right, top, bottom float64) { blk.margins.bottom = bottom } -// Return block Margins: left, right, top, bottom Margins. +// GetMargins returns the Block's margins: left, right, top, bottom. func (blk *Block) GetMargins() (float64, float64, float64, float64) { return blk.margins.left, blk.margins.right, blk.margins.top, blk.margins.bottom } -// Set block positioning to absolute and set the absolute position coordinates as specified. +// SetPos sets the Block's positioning to absolute mode with the specified coordinates. func (blk *Block) SetPos(x, y float64) { blk.positioning = positionAbsolute blk.xPos = x @@ -222,13 +223,13 @@ func (blk *Block) Scale(sx, sy float64) { blk.height *= sy } -// Scale to a specified width, maintaining aspect ratio. +// ScaleToWidth scales the Block to a specified width, maintaining the same aspect ratio. func (blk *Block) ScaleToWidth(w float64) { ratio := w / blk.width blk.Scale(ratio, ratio) } -// Scale to a specified height, maintaining aspect ratio. +// ScaleToHeight scales the Block to a specified height, maintaining the same aspect ratio. func (blk *Block) ScaleToHeight(h float64) { ratio := h / blk.height blk.Scale(ratio, ratio) @@ -308,7 +309,7 @@ func (blk *Block) Draw(d Drawable) error { return nil } -// Draw with context. +// DrawWithContext draws the Block using the specified drawing context. func (blk *Block) DrawWithContext(d Drawable, ctx DrawContext) error { blocks, _, err := d.GeneratePageBlocks(ctx) if err != nil { diff --git a/pdf/creator/chapters.go b/pdf/creator/chapters.go index 0da08756..b0c86459 100644 --- a/pdf/creator/chapters.go +++ b/pdf/creator/chapters.go @@ -13,10 +13,12 @@ import ( "github.com/unidoc/unidoc/pdf/model/fonts" ) +// Chapter is used to arrange multiple drawables (paragraphs, images, etc) into a single section. The concept is +// the same as a book or a report chapter. type Chapter struct { number int title string - heading *paragraph + heading *Paragraph subchapters int @@ -41,6 +43,7 @@ type Chapter struct { toc *TableOfContents } +// NewChapter creates a new chapter with the specified title as the heading. func (c *Creator) NewChapter(title string) *Chapter { chap := &Chapter{} @@ -65,7 +68,7 @@ func (c *Creator) NewChapter(title string) *Chapter { return chap } -// Set flag to indicate whether or not to show chapter numbers as part of title. +// SetShowNumbering sets a flag to indicate whether or not to show chapter numbers as part of title. func (chap *Chapter) SetShowNumbering(show bool) { if show { heading := fmt.Sprintf("%d. %s", chap.number, chap.title) @@ -77,26 +80,18 @@ func (chap *Chapter) SetShowNumbering(show bool) { chap.showNumbering = show } -// Set flag to indicate whether or not to include in tOC. +// SetIncludeInTOC sets a flag to indicate whether or not to include in tOC. func (chap *Chapter) SetIncludeInTOC(includeInTOC bool) { chap.includeInTOC = includeInTOC } -// Get access to the heading paragraph to address style etc. -func (chap *Chapter) GetHeading() *paragraph { +// GetHeading returns the chapter heading paragraph. Used to give access to address style: font, sizing etc. +func (chap *Chapter) GetHeading() *Paragraph { return chap.heading } -/* -// Set absolute coordinates. -func (chap *Chapter) SetPos(x, y float64) { - chap.positioning = positionAbsolute - chap.xPos = x - chap.yPos = y -} -*/ - -// Set chapter Margins. Typically not needed as the Page Margins are used. +// SetMargins sets the Chapter margins: left, right, top, bottom. +// Typically not needed as the creator's page margins are used. func (chap *Chapter) SetMargins(left, right, top, bottom float64) { chap.margins.left = left chap.margins.right = right @@ -104,12 +99,12 @@ func (chap *Chapter) SetMargins(left, right, top, bottom float64) { chap.margins.bottom = bottom } -// Get chapter Margins: left, right, top, bottom. +// GetMargins returns the Chapter's margin: left, right, top, bottom. func (chap *Chapter) GetMargins() (float64, float64, float64, float64) { return chap.margins.left, chap.margins.right, chap.margins.top, chap.margins.bottom } -// Add a new drawable to the chapter. +// Add adds a new Drawable to the chapter. func (chap *Chapter) Add(d Drawable) error { if Drawable(chap) == d { common.Log.Debug("ERROR: Cannot add itself") @@ -120,7 +115,7 @@ func (chap *Chapter) Add(d Drawable) error { case *Chapter: common.Log.Debug("Error: Cannot add chapter to a chapter") return errors.New("Type check error") - case *paragraph, *image, *Block, *subchapter, *Table: + case *Paragraph, *Image, *Block, *Subchapter, *Table: chap.contents = append(chap.contents, d) default: common.Log.Debug("Unsupported: %T", d) @@ -130,7 +125,7 @@ func (chap *Chapter) Add(d Drawable) error { return nil } -// Generate the Page blocks. Multiple blocks are generated if the contents wrap over +// GeneratePageBlocks generate the Page blocks. Multiple blocks are generated if the contents wrap over // multiple pages. func (chap *Chapter) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { origCtx := ctx diff --git a/pdf/creator/color.go b/pdf/creator/color.go index 134e98ed..55cfa2e2 100644 --- a/pdf/creator/color.go +++ b/pdf/creator/color.go @@ -13,8 +13,8 @@ import ( "github.com/unidoc/unidoc/common" ) -// Represents colors in the PDF creator. -type color interface { +// Color interface represents colors in the PDF creator. +type Color interface { ToRGB() (float64, float64, float64) } @@ -38,10 +38,10 @@ var ( ColorYellow = ColorRGBFromArithmetic(1, 1, 0) ) -// Convert color hex code to rgb color for using with creator. +// ColorRGBFromHex converts color hex code to rgb color for using with creator. // NOTE: If there is a problem interpreting the string, then will use black color and log a debug message. // Example hex code: #ffffff -> (1,1,1) white. -func ColorRGBFromHex(hexStr string) color { +func ColorRGBFromHex(hexStr string) Color { color := rgbColor{} if (len(hexStr) != 4 && len(hexStr) != 7) || hexStr[0] != '#' { common.Log.Debug("Invalid hex code: %s", hexStr) @@ -90,10 +90,10 @@ func ColorRGBFromHex(hexStr string) color { return color } -// Color from 8bit (0-255) r,g,b values. +// ColorRGBFrom8bit creates a Color from 8bit (0-255) r,g,b values. // Example: // red := ColorRGBFrom8Bit(255, 0, 0) -func ColorRGBFrom8bit(r, g, b byte) color { +func ColorRGBFrom8bit(r, g, b byte) Color { color := rgbColor{} color.r = float64(r) / 255.0 color.g = float64(g) / 255.0 @@ -101,10 +101,10 @@ func ColorRGBFrom8bit(r, g, b byte) color { return color } -// Color from arithmetic (0-1.0) color values. +// ColorRGBFromArithmetic creates a Color from arithmetic (0-1.0) color values. // Example: // green := ColorRGBFromArithmetic(0, 1.0, 0) -func ColorRGBFromArithmetic(r, g, b float64) color { +func ColorRGBFromArithmetic(r, g, b float64) Color { // Ensure is in the range 0-1: r = math.Max(math.Min(r, 1.0), 0.0) g = math.Max(math.Min(g, 1.0), 0.0) diff --git a/pdf/creator/const.go b/pdf/creator/const.go index 3ac8c2cd..b923c67f 100644 --- a/pdf/creator/const.go +++ b/pdf/creator/const.go @@ -5,10 +5,13 @@ package creator +// PageSize represents the page size as a 2 element array representing the width and height in PDF document units (points). type PageSize [2]float64 -// Default PDF resolution (points/inch, points/mm). -var PPI float64 = 72 // Points per inch. (Default resolution). +// PPI specifies the default PDF resolution in points/inch. +var PPI float64 = 72 // Points per inch. (Default resolution). + +// PPMM specifies the default PDF resolution in points/mm. var PPMM float64 = 72 * 1.0 / 25.4 // Points per mm. (Default resolution). // @@ -25,6 +28,11 @@ var ( // TextAlignment options for paragraph. type TextAlignment int +// The options supported for text alignment are: +// left - TextAlignmentLeft +// right - TextAlignmentRight +// center - TextAlignmentCenter +// justify - TextAlignmentJustify const ( TextAlignmentLeft TextAlignment = iota TextAlignmentRight diff --git a/pdf/creator/creator.go b/pdf/creator/creator.go index 3c58c782..f5093ded 100644 --- a/pdf/creator/creator.go +++ b/pdf/creator/creator.go @@ -14,10 +14,8 @@ import ( "github.com/unidoc/unidoc/pdf/model" ) -// -// The content creator is a wrapper around functionality for creating PDF reports and/or adding new -// content onto imported PDF pages. -// +// Creator is a wrapper around functionality for creating PDF reports and/or adding new +// content onto imported PDF pages, etc. type Creator struct { pages []*model.PdfPage activePage *model.PdfPage @@ -33,29 +31,34 @@ type Creator struct { // Keep track of number of chapters for indexing. chapters int + // Hooks. genFrontPageFunc func(args FrontpageFunctionArgs) genTableOfContentFunc func(toc *TableOfContents) (*Chapter, error) drawHeaderFunc func(header *Block, args HeaderFunctionArgs) drawFooterFunc func(footer *Block, args FooterFunctionArgs) + pdfWriterAccessFunc func(writer *model.PdfWriter) error finalized bool toc *TableOfContents } -// Input arguments to a front page drawing function. +// FrontpageFunctionArgs holds the input arguments to a front page drawing function. +// It is designed as a struct, so additional parameters can be added in the future with backwards compatibility. type FrontpageFunctionArgs struct { PageNum int TotalPages int } -// Input arguments to a header drawing function. +// HeaderFunctionArgs holds the input arguments to a header drawing function. +// It is designed as a struct, so additional parameters can be added in the future with backwards compatibility. type HeaderFunctionArgs struct { PageNum int TotalPages int } -// Input arguments to a footer drawing function. +// FooterFunctionArgs holds the input arguments to a footer drawing function. +// It is designed as a struct, so additional parameters can be added in the future with backwards compatibility. type FooterFunctionArgs struct { PageNum int TotalPages int @@ -69,7 +72,7 @@ type margins struct { bottom float64 } -// Create a new instance of the PDF creator. +// New creates a new instance of the PDF Creator. func New() *Creator { c := &Creator{} c.pages = []*model.PdfPage{} @@ -86,7 +89,7 @@ func New() *Creator { return c } -// Set the page margins: left, right, top, bottom. +// SetPageMargins sets the page margins: left, right, top, bottom. // The default page margins are 10% of document width. func (c *Creator) SetPageMargins(left, right, top, bottom float64) { c.pageMargins.left = left @@ -95,12 +98,12 @@ func (c *Creator) SetPageMargins(left, right, top, bottom float64) { c.pageMargins.bottom = bottom } -// Returns the current Page width. +// Width returns the current page width. func (c *Creator) Width() float64 { return c.pageWidth } -// Returns the current Page height. +// Height returns the current page height. func (c *Creator) Height() float64 { return c.pageHeight } @@ -115,12 +118,11 @@ func (c *Creator) getActivePage() *model.PdfPage { return nil } return c.pages[len(c.pages)-1] - } else { - return c.activePage } + return c.activePage } -// Set a new Page size. Pages that are added after this will be created with this Page size. +// SetPageSize sets the Creator's page size. Pages that are added after this will be created with this Page size. // Does not affect pages already created. // // Common page sizes are defined as constants. @@ -151,22 +153,22 @@ func (c *Creator) SetPageSize(size PageSize) { c.pageMargins.bottom = m } -// Set a function to draw a header on created output pages. +// DrawHeader sets a function to draw a header on created output pages. func (c *Creator) DrawHeader(drawHeaderFunc func(header *Block, args HeaderFunctionArgs)) { c.drawHeaderFunc = drawHeaderFunc } -// Set a function to draw a footer on created output pages. +// DrawFooter sets a function to draw a footer on created output pages. func (c *Creator) DrawFooter(drawFooterFunc func(footer *Block, args FooterFunctionArgs)) { c.drawFooterFunc = drawFooterFunc } -// Set a function to generate a front Page. +// CreateFrontPage sets a function to generate a front Page. func (c *Creator) CreateFrontPage(genFrontPageFunc func(args FrontpageFunctionArgs)) { c.genFrontPageFunc = genFrontPageFunc } -// Seta function to generate table of contents. +// CreateTableOfContents sets a function to generate table of contents. func (c *Creator) CreateTableOfContents(genTOCFunc func(toc *TableOfContents) (*Chapter, error)) { c.genTableOfContentFunc = genTOCFunc } @@ -201,13 +203,14 @@ func (c *Creator) initContext() { c.context.Margins = c.pageMargins } -// Adds a new Page to the creator and sets as the active Page. +// NewPage adds a new Page to the Creator and sets as the active Page. func (c *Creator) NewPage() { page := c.newPage() c.pages = append(c.pages, page) c.context.Page++ } +// AddPage adds the specified page to the creator. func (c *Creator) AddPage(page *model.PdfPage) error { mbox, err := page.GetMediaBox() if err != nil { @@ -226,8 +229,8 @@ func (c *Creator) AddPage(page *model.PdfPage) error { return nil } -// Rotate the current active page by angle degrees. -// NOTE: angleDeg must be a multiple of 90. +// RotateDeg rotates the current active page by angle degrees. An error is returned on failure, which can be +// if there is no currently active page, or the angleDeg is not a multiple of 90 degrees. func (c *Creator) RotateDeg(angleDeg int64) error { page := c.getActivePage() if page == nil { @@ -250,7 +253,7 @@ func (c *Creator) RotateDeg(angleDeg int64) error { return nil } -// Get current context. +// Context returns the current drawing context. func (c *Creator) Context() DrawContext { return c.context } @@ -282,7 +285,7 @@ func (c *Creator) finalize() error { genpages += len(blocks) // Update the table of content Page numbers, accounting for front Page and TOC. - for idx, _ := range c.toc.entries { + for idx := range c.toc.entries { c.toc.entries[idx].PageNumber += genpages } @@ -384,33 +387,33 @@ func (c *Creator) finalize() error { return nil } -// Move absolute position to x, y. +// MoveTo moves the drawing context to absolute coordinates (x, y). func (c *Creator) MoveTo(x, y float64) { c.context.X = x c.context.Y = y } -// Move draw context to absolute position x. +// MoveX moves the drawing context to absolute position x. func (c *Creator) MoveX(x float64) { c.context.X = x } -// Move draw context to absolute position y. +// MoveY moves the drawing context to absolute position y. func (c *Creator) MoveY(y float64) { c.context.Y = y } -// Move draw context right by relative position dx (negative goes left). +// MoveRight moves the drawing context right by relative displacement dx (negative goes left). func (c *Creator) MoveRight(dx float64) { c.context.X += dx } -// Move draw context down by relative position dy (negative goes up). +// MoveDown moves the drawing context down by relative displacement dy (negative goes up). func (c *Creator) MoveDown(dy float64) { c.context.Y += dy } -// Draw the drawable widget to the document. This can span over 1 or more pages. Additional pages are added if +// Draw draws the Drawable widget to the document. This can span over 1 or more pages. Additional pages are added if // the contents go over the current Page. func (c *Creator) Draw(d Drawable) error { if c.getActivePage() == nil { @@ -451,6 +454,15 @@ func (c *Creator) Write(ws io.WriteSeeker) error { pdfWriter := model.NewPdfWriter() + // Pdf Writer access hook. Can be used to encrypt, etc. via the PdfWriter instance. + if c.pdfWriterAccessFunc != nil { + err := c.pdfWriterAccessFunc(&pdfWriter) + if err != nil { + common.Log.Debug("Failure: %v", err) + return err + } + } + for _, page := range c.pages { err := pdfWriter.AddPage(page) if err != nil { @@ -467,7 +479,24 @@ func (c *Creator) Write(ws io.WriteSeeker) error { return nil } -// Write output of creator to file. +// SetPdfWriterAccessFunc sets a PdfWriter access function/hook. +// Exposes the PdfWriter just prior to writing the PDF. Can be used to encrypt the output PDF, etc. +// +// Example of encrypting with a user/owner password "password" +// Prior to calling c.WriteFile(): +// +// c.SetPdfWriterAccessFunc(func(w *model.PdfWriter) error { +// userPass := []byte("password") +// ownerPass := []byte("password") +// err := w.Encrypt(userPass, ownerPass, nil) +// return err +// }) +// +func (c *Creator) SetPdfWriterAccessFunc(pdfWriterAccessFunc func(writer *model.PdfWriter) error) { + c.pdfWriterAccessFunc = pdfWriterAccessFunc +} + +// WriteToFile writes the Creator output to file specified by path. func (c *Creator) WriteToFile(outputPath string) error { fWrite, err := os.Create(outputPath) if err != nil { diff --git a/pdf/creator/creator_test.go b/pdf/creator/creator_test.go index 82bfdd5e..c3477d90 100644 --- a/pdf/creator/creator_test.go +++ b/pdf/creator/creator_test.go @@ -34,6 +34,8 @@ const testPdfLoremIpsumFile = "../../testfiles/lorem.pdf" const testPdfTemplatesFile1 = "../../testfiles/templates1.pdf" const testImageFile1 = "../../testfiles/logo.png" const testImageFile2 = "../../testfiles/signature.png" +const testRobotoRegularTTFFile = "../../testfiles/roboto/Roboto-Regular.ttf" +const testRobotoBoldTTFFile = "../../testfiles/roboto/Roboto-Bold.ttf" func TestTemplate1(t *testing.T) { creator := New() @@ -437,23 +439,23 @@ func TestParagraphWrapping2(t *testing.T) { } } -// Test writing with various TTF fonts. Assumes MacOS system, where fonts are stored under /Library/Fonts. +// Test writing with TTF fonts. func TestParagraphFonts(t *testing.T) { creator := New() - verdana, err := model.NewPdfFontFromTTFFile("/Library/Fonts/Verdana.ttf") + roboto, err := model.NewPdfFontFromTTFFile(testRobotoRegularTTFFile) if err != nil { t.Errorf("Fail: %v\n", err) return } - arialBold, err := model.NewPdfFontFromTTFFile("/Library/Fonts/Arial Bold.ttf") + robotoBold, err := model.NewPdfFontFromTTFFile(testRobotoBoldTTFFile) if err != nil { t.Errorf("Fail: %v\n", err) return } - fonts := []fonts.Font{verdana, arialBold, fonts.NewFontHelvetica(), verdana, arialBold, fonts.NewFontHelvetica()} + fonts := []fonts.Font{roboto, robotoBold, fonts.NewFontHelvetica(), roboto, robotoBold, fonts.NewFontHelvetica()} for _, font := range fonts { p := NewParagraph("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt" + "ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " + @@ -1263,3 +1265,36 @@ func TestQRCodeOnTemplate(t *testing.T) { // Write the example to file. creator.WriteToFile("/tmp/4_barcode_on_tpl.pdf") } + +// Test adding encryption to output. +func TestEncrypting1(t *testing.T) { + c := New() + + ch1 := c.NewChapter("Introduction") + + p := NewParagraph("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " + + "ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " + + "aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore " + + "eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + + "mollit anim id est laborum.") + p.SetMargins(0, 0, 10, 0) + + for j := 0; j < 55; j++ { + ch1.Add(p) // Can add any drawable.. + } + + c.Draw(ch1) + + c.SetPdfWriterAccessFunc(func(w *model.PdfWriter) error { + userPass := []byte("password") + ownerPass := []byte("password") + err := w.Encrypt(userPass, ownerPass, nil) + return err + }) + + err := c.WriteToFile("/tmp/6_chapters_encrypted_password.pdf") + if err != nil { + t.Errorf("Fail: %v\n", err) + return + } +} diff --git a/pdf/creator/doc.go b/pdf/creator/doc.go index b06f8127..b37c898f 100644 --- a/pdf/creator/doc.go +++ b/pdf/creator/doc.go @@ -4,7 +4,7 @@ */ // -// The creator is used for quickly generating pages and content with a simple interface. +// Package creator is used for quickly generating pages and content with a simple interface. // It is built on top of the model package to provide access to the most common // operations such as creating text and image reports and manipulating existing pages. // diff --git a/pdf/creator/drawable.go b/pdf/creator/drawable.go index 722baa1f..fed0ec18 100644 --- a/pdf/creator/drawable.go +++ b/pdf/creator/drawable.go @@ -5,7 +5,7 @@ package creator -// All widgets that can be used to draw with the creator need to implement the Drawable interface. +// Drawable is a widget that can be used to draw with the Creator. type Drawable interface { // Draw onto blocks representing Page contents. As the content can wrap over many pages, multiple // templates are returned, one per Page. The function also takes a draw context containing information @@ -13,15 +13,16 @@ type Drawable interface { GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) } -// A vector drawable is a Drawable with a specified width and height. +// VectorDrawable is a Drawable with a specified width and height. type VectorDrawable interface { Drawable Width() float64 Height() float64 } -// Drawing context. Continuously used when drawing the page contents. Keeps track of current X, Y position, -// available height as well as other page parameters such as margins and dimensions. +// DrawContext defines the drawing context. The DrawContext is continuously used and updated when drawing the page +// contents in relative mode. Keeps track of current X, Y position, available height as well as other page parameters +// such as margins and dimensions. type DrawContext struct { // Current page number. Page int diff --git a/pdf/creator/ellipse.go b/pdf/creator/ellipse.go index 6c75fa22..863d2a89 100644 --- a/pdf/creator/ellipse.go +++ b/pdf/creator/ellipse.go @@ -10,12 +10,10 @@ import ( "github.com/unidoc/unidoc/pdf/model" ) -// -// Defines an ellipse with a center at (xc,yc) and a specified width and height. The ellipse can have a colored +// Ellipse defines an ellipse with a center at (xc,yc) and a specified width and height. The ellipse can have a colored // fill and/or border with a specified width. // Implements the Drawable interface and can be drawn on PDF using the Creator. -// -type ellipse struct { +type Ellipse struct { xc float64 yc float64 width float64 @@ -25,9 +23,9 @@ type ellipse struct { borderWidth float64 } -// Generates a new ellipse centered at (xc,yc) with a width and height specified. -func NewEllipse(xc, yc, width, height float64) *ellipse { - ell := &ellipse{} +// NewEllipse creates a new ellipse centered at (xc,yc) with a width and height specified. +func NewEllipse(xc, yc, width, height float64) *Ellipse { + ell := &Ellipse{} ell.xc = xc ell.yc = yc @@ -40,28 +38,28 @@ func NewEllipse(xc, yc, width, height float64) *ellipse { return ell } -// Get the coordinates of the ellipse center (xc,yc). -func (ell *ellipse) GetCoords() (float64, float64) { +// GetCoords returns the coordinates of the Ellipse's center (xc,yc). +func (ell *Ellipse) GetCoords() (float64, float64) { return ell.xc, ell.yc } -// Set border width. -func (ell *ellipse) SetBorderWidth(bw float64) { +// SetBorderWidth sets the border width. +func (ell *Ellipse) SetBorderWidth(bw float64) { ell.borderWidth = bw } -// Set border color. -func (ell *ellipse) SetBorderColor(col color) { +// SetBorderColor sets the border color. +func (ell *Ellipse) SetBorderColor(col Color) { ell.borderColor = model.NewPdfColorDeviceRGB(col.ToRGB()) } -// Set fill color. -func (ell *ellipse) SetFillColor(col color) { +// SetFillColor sets the fill color. +func (ell *Ellipse) SetFillColor(col Color) { ell.fillColor = model.NewPdfColorDeviceRGB(col.ToRGB()) } -// Draws the rectangle on a new block representing the page. -func (ell *ellipse) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { +// GeneratePageBlocks draws the rectangle on a new block representing the page. +func (ell *Ellipse) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { block := NewBlock(ctx.PageWidth, ctx.PageHeight) drawell := draw.Circle{ diff --git a/pdf/creator/image.go b/pdf/creator/image.go index 97b49979..01eee1c7 100644 --- a/pdf/creator/image.go +++ b/pdf/creator/image.go @@ -18,8 +18,8 @@ import ( "github.com/unidoc/unidoc/pdf/model" ) -// The image type is used to draw an image onto PDF. -type image struct { +// The Image type is used to draw an image onto PDF. +type Image struct { xobj *model.XObjectImage // Rotation angle. @@ -48,9 +48,9 @@ type image struct { rotOriginX, rotOriginY float64 } -// Create a new image from a unidoc image model. -func NewImage(img *model.Image) (*image, error) { - image := &image{} +// NewImage create a new image from a unidoc image (model.Image). +func NewImage(img *model.Image) (*Image, error) { + image := &Image{} // Create the XObject image. ximg, err := model.NewXObjectImageFromImage(img, nil, core.NewFlateEncoder()) @@ -74,8 +74,8 @@ func NewImage(img *model.Image) (*image, error) { return image, nil } -// Create an image from image data. -func NewImageFromData(data []byte) (*image, error) { +// NewImageFromData creates an Image from image data. +func NewImageFromData(data []byte) (*Image, error) { imgReader := bytes.NewReader(data) // Load the image with default handler. @@ -88,8 +88,8 @@ func NewImageFromData(data []byte) (*image, error) { return NewImage(img) } -// Create image from a file. -func NewImageFromFile(path string) (*image, error) { +// NewImageFromFile creates an Image from a file. +func NewImageFromFile(path string) (*Image, error) { imgData, err := ioutil.ReadFile(path) if err != nil { return nil, err @@ -103,8 +103,8 @@ func NewImageFromFile(path string) (*image, error) { return img, nil } -// Create image from a go image.Image datastructure. -func NewImageFromGoImage(goimg goimage.Image) (*image, error) { +// NewImageFromGoImage creates an Image from a go image.Image datastructure. +func NewImageFromGoImage(goimg goimage.Image) (*Image, error) { img, err := model.ImageHandling.NewImageFromGoImage(goimg) if err != nil { return nil, err @@ -113,36 +113,36 @@ func NewImageFromGoImage(goimg goimage.Image) (*image, error) { return NewImage(img) } -// Get image document height. -func (img *image) Height() float64 { +// Height returns Image's document height. +func (img *Image) Height() float64 { return img.height } -// Get image document width. -func (img *image) Width() float64 { +// Width returns Image's document width. +func (img *Image) Width() float64 { return img.width } -// Set opacity -func (img *image) SetOpacity(opacity float64) { +// SetOpacity sets opacity for Image. +func (img *Image) SetOpacity(opacity float64) { img.opacity = opacity } -// Set the image Margins. -func (img *image) SetMargins(left, right, top, bottom float64) { +// SetMargins sets the margins for the Image (in relative mode): left, right, top, bottom. +func (img *Image) SetMargins(left, right, top, bottom float64) { img.margins.left = left img.margins.right = right img.margins.top = top img.margins.bottom = bottom } -// Get image Margins: left, right, top, bottom. -func (img *image) GetMargins() (float64, float64, float64, float64) { +// GetMargins returns the Image's margins: left, right, top, bottom. +func (img *Image) GetMargins() (float64, float64, float64, float64) { return img.margins.left, img.margins.right, img.margins.top, img.margins.bottom } -// Generate the Page blocks. -func (img *image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { +// GeneratePageBlocks generate the Page blocks. Draws the Image on a block, implementing the Drawable interface. +func (img *Image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { blocks := []*Block{} origCtx := ctx @@ -163,6 +163,11 @@ func (img *image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, er newContext.Height = ctx.PageHeight - ctx.Margins.top - ctx.Margins.bottom - img.margins.bottom newContext.Width = ctx.PageWidth - ctx.Margins.left - ctx.Margins.right - img.margins.left - img.margins.right ctx = newContext + } else { + ctx.Y += img.margins.top + ctx.Height -= img.margins.top + img.margins.bottom + ctx.X += img.margins.left + ctx.Width -= img.margins.left + img.margins.right } } else { // Absolute. @@ -170,7 +175,7 @@ func (img *image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, er ctx.Y = img.yPos } - // Place the image on the template at position (x,y) based on the ctx. + // Place the Image on the template at position (x,y) based on the ctx. ctx, err := drawImageOnBlock(blk, img, ctx) if err != nil { return nil, ctx, err @@ -183,56 +188,58 @@ func (img *image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, er ctx = origCtx } else { // XXX/TODO: Use projected height. - ctx.Y += img.height + ctx.Y += img.margins.bottom + ctx.Height -= img.margins.bottom } return blocks, ctx, nil } -// Set absolute position. Changes object positioning to absolute. -func (img *image) SetPos(x, y float64) { +// SetPos sets the absolute position. Changes object positioning to absolute. +func (img *Image) SetPos(x, y float64) { img.positioning = positionAbsolute img.xPos = x img.yPos = y } -// Scale image by a constant factor, both width and height. -func (img *image) Scale(xFactor, yFactor float64) { +// Scale scales Image by a constant factor, both width and height. +func (img *Image) Scale(xFactor, yFactor float64) { img.width = xFactor * img.width img.height = yFactor * img.height } -// Scale image to a specified width w, maintaining the aspect ratio. -func (img *image) ScaleToWidth(w float64) { +// ScaleToWidth scale Image to a specified width w, maintaining the aspect ratio. +func (img *Image) ScaleToWidth(w float64) { ratio := img.height / img.width img.width = w img.height = w * ratio } -// Scale image to a specified height h, maintaining the aspect ratio. -func (img *image) ScaleToHeight(h float64) { +// ScaleToHeight scale Image to a specified height h, maintaining the aspect ratio. +func (img *Image) ScaleToHeight(h float64) { ratio := img.width / img.height img.height = h img.width = h * ratio } -// Set the image document width to specified w. -func (img *image) SetWidth(w float64) { +// SetWidth set the Image's document width to specified w. This does not change the raw image data, i.e. +// no actual scaling of data is performed. That is handled by the PDF viewer. +func (img *Image) SetWidth(w float64) { img.width = w } -// Set the image document height to specified h. -func (img *image) SetHeight(h float64) { +// SetHeight sets the Image's document height to specified h. +func (img *Image) SetHeight(h float64) { img.height = h } -// Set image rotation angle in degrees. -func (img *image) SetAngle(angle float64) { +// SetAngle sets Image rotation angle in degrees. +func (img *Image) SetAngle(angle float64) { img.angle = angle } // Draw the image onto the specified blk. -func drawImageOnBlock(blk *Block, img *image, ctx DrawContext) (DrawContext, error) { +func drawImageOnBlock(blk *Block, img *Image, ctx DrawContext) (DrawContext, error) { origCtx := ctx // Find a free name for the image. @@ -296,9 +303,9 @@ func drawImageOnBlock(blk *Block, img *image, ctx DrawContext) (DrawContext, err blk.addContents(ops) - ctx.Y += img.Height() - if img.positioning.isRelative() { + ctx.Y += img.Height() + ctx.Height -= img.Height() return ctx, nil } else { // Absolute positioning - return original context. diff --git a/pdf/creator/line.go b/pdf/creator/line.go index 6b5d8a6c..ab908b13 100644 --- a/pdf/creator/line.go +++ b/pdf/creator/line.go @@ -12,12 +12,10 @@ import ( "github.com/unidoc/unidoc/pdf/model" ) -// -// Defines a line between point 1 (X1,Y1) and point 2 (X2,Y2). The line ending styles can be none (regular line), +// Line defines a line between point 1 (X1,Y1) and point 2 (X2,Y2). The line ending styles can be none (regular line), // or arrows at either end. The line also has a specified width, color and opacity. // Implements the Drawable interface and can be drawn on PDF using the Creator. -// -type line struct { +type Line struct { x1 float64 y1 float64 x2 float64 @@ -26,9 +24,9 @@ type line struct { lineWidth float64 } -// Generate a new line with default parameters between (x1,y1) to (x2,y2). -func NewLine(x1, y1, x2, y2 float64) *line { - l := &line{} +// NewLine creates a new Line with default parameters between (x1,y1) to (x2,y2). +func NewLine(x1, y1, x2, y2 float64) *Line { + l := &Line{} l.x1 = x1 l.y1 = y1 @@ -41,29 +39,29 @@ func NewLine(x1, y1, x2, y2 float64) *line { return l } -// Get the (x1, y1), (x2, y2) points defining the line. -func (l *line) GetCoords() (float64, float64, float64, float64) { +// GetCoords returns the (x1, y1), (x2, y2) points defining the Line. +func (l *Line) GetCoords() (float64, float64, float64, float64) { return l.x1, l.y1, l.x2, l.y2 } -// Set line width. -func (l *line) SetLineWidth(lw float64) { +// SetLineWidth sets the line width. +func (l *Line) SetLineWidth(lw float64) { l.lineWidth = lw } -// Set line color. +// SetColor sets the line color. // Use ColorRGBFromHex, ColorRGBFrom8bit or ColorRGBFromArithmetic to make the color object. -func (l *line) SetColor(col color) { +func (l *Line) SetColor(col Color) { l.lineColor = model.NewPdfColorDeviceRGB(col.ToRGB()) } -// Calculate line length. -func (l *line) Length() float64 { +// Length calculates and returns the line length. +func (l *Line) Length() float64 { return math.Sqrt(math.Pow(l.x2-l.x1, 2.0) + math.Pow(l.y2-l.y1, 2.0)) } -// Draws the line on a new block representing the page. -func (l *line) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { +// GeneratePageBlocks draws the line on a new block representing the page. Implements the Drawable interface. +func (l *Line) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { block := NewBlock(ctx.PageWidth, ctx.PageHeight) drawline := draw.Line{ diff --git a/pdf/creator/paragraph.go b/pdf/creator/paragraph.go index 46068bfa..80a4b071 100644 --- a/pdf/creator/paragraph.go +++ b/pdf/creator/paragraph.go @@ -17,9 +17,9 @@ import ( "github.com/unidoc/unidoc/pdf/model/textencoding" ) -// A paragraph represents text drawn with a specified font and can wrap across lines and pages. +// Paragraph represents text drawn with a specified font and can wrap across lines and pages. // By default occupies the available width in the drawing context. -type paragraph struct { +type Paragraph struct { // The input utf-8 text as a string (series of runes). text string @@ -65,10 +65,10 @@ type paragraph struct { textLines []string } -// Create a new text block. Uses default parameters: Helvetica, WinAnsiEncoding and wrap enabled +// NewParagraph create a new text paragraph. Uses default parameters: Helvetica, WinAnsiEncoding and wrap enabled // with a wrap width of 100 points. -func NewParagraph(text string) *paragraph { - p := ¶graph{} +func NewParagraph(text string) *Paragraph { + p := &Paragraph{} p.text = text p.textFont = fonts.NewFontHelvetica() p.SetEncoder(textencoding.NewWinAnsiTextEncoder()) @@ -89,92 +89,96 @@ func NewParagraph(text string) *paragraph { return p } -func (p *paragraph) SetFont(font fonts.Font) { +// SetFont sets the Paragraph's font. +func (p *Paragraph) SetFont(font fonts.Font) { p.textFont = font } -func (p *paragraph) SetFontSize(fontSize float64) { +// SetFontSize sets the font size in document units (points). +func (p *Paragraph) SetFontSize(fontSize float64) { p.fontSize = fontSize } -// Alignment of the text within the width provided. -func (p *paragraph) SetTextAlignment(align TextAlignment) { +// SetTextAlignment sets the horizontal alignment of the text within the space provided. +func (p *Paragraph) SetTextAlignment(align TextAlignment) { p.alignment = align } -// Set text encoding. -func (p *paragraph) SetEncoder(encoder textencoding.TextEncoder) { +// SetEncoder sets the text encoding. +func (p *Paragraph) SetEncoder(encoder textencoding.TextEncoder) { p.encoder = encoder // Sync with the text font too. // XXX/FIXME: Keep in 1 place only. p.textFont.SetEncoder(encoder) } -func (p *paragraph) SetLineHeight(lineheight float64) { +// SetLineHeight sets the line height (1.0 default). +func (p *Paragraph) SetLineHeight(lineheight float64) { p.lineHeight = lineheight } -func (p *paragraph) SetText(text string) { +// SetText sets the text content of the Paragraph. +func (p *Paragraph) SetText(text string) { p.text = text } -// Set line wrapping enabled flag. -func (p *paragraph) SetEnableWrap(enableWrap bool) { +// SetEnableWrap sets the line wrapping enabled flag. +func (p *Paragraph) SetEnableWrap(enableWrap bool) { p.enableWrap = enableWrap } -// Set color of paragraph text. +// SetColor set the color of the Paragraph text. // // Example: // 1. p := NewParagraph("Red paragraph") // // Set to red color with a hex code: // p.SetColor(creator.ColorRGBFromHex("#ff0000")) // -// 2. Make paragraph green with 8-bit rgb values (0-255 each component) +// 2. Make Paragraph green with 8-bit rgb values (0-255 each component) // p.SetColor(creator.ColorRGBFrom8bit(0, 255, 0) // -// 3. Make paragraph blue with arithmetic (0-1) rgb components. +// 3. Make Paragraph blue with arithmetic (0-1) rgb components. // p.SetColor(creator.ColorRGBFromArithmetic(0, 0, 1.0) // -func (p *paragraph) SetColor(col color) { +func (p *Paragraph) SetColor(col Color) { pdfColor := model.NewPdfColorDeviceRGB(col.ToRGB()) p.color = *pdfColor } -// Drawable interface implementations. - -// Set absolute positioning with specified coordinates. -func (p *paragraph) SetPos(x, y float64) { +// SetPos sets absolute positioning with specified coordinates. +func (p *Paragraph) SetPos(x, y float64) { p.positioning = positionAbsolute p.xPos = x p.yPos = y } -// Set rotation angle. -func (p *paragraph) SetAngle(angle float64) { +// SetAngle sets the rotation angle of the text. +func (p *Paragraph) SetAngle(angle float64) { p.angle = angle } -// Set paragraph Margins. -func (p *paragraph) SetMargins(left, right, top, bottom float64) { +// SetMargins sets the Paragraph's margins. +func (p *Paragraph) SetMargins(left, right, top, bottom float64) { p.margins.left = left p.margins.right = right p.margins.top = top p.margins.bottom = bottom } -// Get paragraph Margins: left, right, top, bottom. -func (p *paragraph) GetMargins() (float64, float64, float64, float64) { +// GetMargins returns the Paragraph's margins: left, right, top, bottom. +func (p *Paragraph) GetMargins() (float64, float64, float64, float64) { return p.margins.left, p.margins.right, p.margins.top, p.margins.bottom } -// Set the paragraph width. Esentially the wrapping width, the width the text can extend to prior to wrapping. -func (p *paragraph) SetWidth(width float64) { +// SetWidth sets the the Paragraph width. This is essentially the wrapping width, i.e. the width the text can extend to +// prior to wrapping over to next line. +func (p *Paragraph) SetWidth(width float64) { p.wrapWidth = width p.wrapText() } -func (p *paragraph) Width() float64 { +// Width returns the width of the Paragraph. +func (p *Paragraph) Width() float64 { if p.enableWrap { return p.wrapWidth } else { @@ -182,9 +186,9 @@ func (p *paragraph) Width() float64 { } } -// The height is calculated based on the input text and how it is wrapped within the container. -// Height does not include Margins. -func (p *paragraph) Height() float64 { +// Height returns the height of the Paragraph. The height is calculated based on the input text and how it is wrapped +// within the container. Does not include Margins. +func (p *Paragraph) Height() float64 { if p.textLines == nil || len(p.textLines) == 0 { p.wrapText() } @@ -193,23 +197,8 @@ func (p *paragraph) Height() float64 { return h } -func (p *paragraph) Scale(sx, sy float64) { - p.scaleX = sx - p.scaleY = sy -} - -func (p *paragraph) ScaleToHeight(h float64) { - ratio := h / p.Height() - p.Scale(ratio, ratio) -} - -func (p *paragraph) ScaleToWidth(w float64) { - ratio := w / p.Width() - p.Scale(ratio, ratio) -} - // Calculate the text width (if not wrapped). -func (p *paragraph) getTextWidth() float64 { +func (p *Paragraph) getTextWidth() float64 { w := float64(0.0) for _, rune := range p.text { @@ -232,7 +221,7 @@ func (p *paragraph) getTextWidth() float64 { // Simple algorithm to wrap the text into lines (greedy algorithm - fill the lines). // XXX/TODO: Consider the Knuth/Plass algorithm or an alternative. -func (p *paragraph) wrapText() error { +func (p *Paragraph) wrapText() error { if !p.enableWrap { p.textLines = []string{p.encoder.Encode(p.text)} return nil @@ -308,15 +297,15 @@ func (p *paragraph) wrapText() error { return nil } -// Generate the Page blocks. Multiple blocks are generated if the contents wrap over -// multiple pages. -func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { +// GeneratePageBlocks generates the page blocks. Multiple blocks are generated if the contents wrap over +// multiple pages. Implements the Drawable interface. +func (p *Paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { origContext := ctx blocks := []*Block{} blk := NewBlock(ctx.PageWidth, ctx.PageHeight) if p.positioning.isRelative() { - // Account for paragraph Margins. + // Account for Paragraph Margins. ctx.X += p.margins.left ctx.Y += p.margins.top ctx.Width -= p.margins.left + p.margins.right @@ -328,7 +317,7 @@ func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, if p.Height() > ctx.Height { // Goes out of the bounds. Write on a new template instead and create a new context at upper // left corner. - // XXX/TODO: Handle case when paragraph is larger than the Page... + // XXX/TODO: Handle case when Paragraph is larger than the Page... // Should be fine if we just break on the paragraph, i.e. splitting it up over 2+ pages blocks = append(blocks, blk) @@ -353,7 +342,7 @@ func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, ctx.Y = p.yPos } - // Place the paragraph on the template at position (x,y) based on the ctx. + // Place the Paragraph on the template at position (x,y) based on the ctx. ctx, err := drawParagraphOnBlock(blk, p, ctx) if err != nil { common.Log.Debug("ERROR: %v", err) @@ -363,6 +352,7 @@ func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, blocks = append(blocks, blk) if p.positioning.isRelative() { ctx.X -= p.margins.left // Move back. + ctx.Width = origContext.Width return blocks, ctx, nil } else { // Absolute: not changing the context. @@ -371,7 +361,7 @@ func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, } // Draw block on specified location on Page, adding to the content stream. -func drawParagraphOnBlock(blk *Block, p *paragraph, ctx DrawContext) (DrawContext, error) { +func drawParagraphOnBlock(blk *Block, p *Paragraph, ctx DrawContext) (DrawContext, error) { // Find a free name for the font. num := 1 fontName := core.PdfObjectName(fmt.Sprintf("Font%d", num)) diff --git a/pdf/creator/rectangle.go b/pdf/creator/rectangle.go index f5309902..35ac6db5 100644 --- a/pdf/creator/rectangle.go +++ b/pdf/creator/rectangle.go @@ -10,12 +10,10 @@ import ( "github.com/unidoc/unidoc/pdf/model" ) -// -// Defines a rectangle with upper left corner at (x,y) and a specified width and height. The rectangle +// Rectangle defines a rectangle with upper left corner at (x,y) and a specified width and height. The rectangle // can have a colored fill and/or border with a specified width. // Implements the Drawable interface and can be drawn on PDF using the Creator. -// -type rectangle struct { +type Rectangle struct { x float64 // Upper left corner y float64 width float64 @@ -25,9 +23,9 @@ type rectangle struct { borderWidth float64 } -// Generate a new line with default parameters between (x1,y1) to (x2,y2). -func NewRectangle(x, y, width, height float64) *rectangle { - rect := &rectangle{} +// NewRectangle creates a new Rectangle with default parameters with left corner at (x,y) and width, height as specified. +func NewRectangle(x, y, width, height float64) *Rectangle { + rect := &Rectangle{} rect.x = x rect.y = y @@ -40,28 +38,28 @@ func NewRectangle(x, y, width, height float64) *rectangle { return rect } -// Get the coords of the upper left corner (x,y). -func (rect *rectangle) GetCoords() (float64, float64) { +// GetCoords returns coordinates of the Rectangle's upper left corner (x,y). +func (rect *Rectangle) GetCoords() (float64, float64) { return rect.x, rect.y } -// Set border width. -func (rect *rectangle) SetBorderWidth(bw float64) { +// SetBorderWidth sets the border width. +func (rect *Rectangle) SetBorderWidth(bw float64) { rect.borderWidth = bw } -// Set border color. -func (rect *rectangle) SetBorderColor(col color) { +// SetBorderColor sets border color. +func (rect *Rectangle) SetBorderColor(col Color) { rect.borderColor = model.NewPdfColorDeviceRGB(col.ToRGB()) } -// Set fill color. -func (rect *rectangle) SetFillColor(col color) { +// SetFillColor sets the fill color. +func (rect *Rectangle) SetFillColor(col Color) { rect.fillColor = model.NewPdfColorDeviceRGB(col.ToRGB()) } -// Draws the rectangle on a new block representing the page. -func (rect *rectangle) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { +// GeneratePageBlocks draws the rectangle on a new block representing the page. Implements the Drawable interface. +func (rect *Rectangle) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { block := NewBlock(ctx.PageWidth, ctx.PageHeight) drawrect := draw.Rectangle{ diff --git a/pdf/creator/subchapter.go b/pdf/creator/subchapter.go index 37cdb67c..f38c7b3e 100644 --- a/pdf/creator/subchapter.go +++ b/pdf/creator/subchapter.go @@ -12,13 +12,13 @@ import ( "github.com/unidoc/unidoc/pdf/model/fonts" ) -// A subchapter simply represents a subchapter pertaining to a specific chapter. It can contain multiple -// drawables, just like a chapter. -type subchapter struct { +// Subchapter simply represents a sub chapter pertaining to a specific Chapter. It can contain multiple +// Drawables, just like a chapter. +type Subchapter struct { chapterNum int subchapterNum int title string - heading *paragraph + heading *Paragraph contents []Drawable @@ -41,8 +41,10 @@ type subchapter struct { toc *TableOfContents } -func (c *Creator) NewSubchapter(ch *Chapter, title string) *subchapter { - subchap := &subchapter{} +// NewSubchapter creates a new Subchapter under Chapter ch with specified title. +// All other parameters are set to their defaults. +func (c *Creator) NewSubchapter(ch *Chapter, title string) *Subchapter { + subchap := &Subchapter{} ch.subchapters++ subchap.subchapterNum = ch.subchapters @@ -71,8 +73,8 @@ func (c *Creator) NewSubchapter(ch *Chapter, title string) *subchapter { return subchap } -// Set flag to indicate whether or not to show chapter numbers as part of title. -func (subchap *subchapter) SetShowNumbering(show bool) { +// SetShowNumbering sets a flag to indicate whether or not to show chapter numbers as part of title. +func (subchap *Subchapter) SetShowNumbering(show bool) { if show { heading := fmt.Sprintf("%d.%d. %s", subchap.chapterNum, subchap.subchapterNum, subchap.title) subchap.heading.SetText(heading) @@ -83,13 +85,13 @@ func (subchap *subchapter) SetShowNumbering(show bool) { subchap.showNumbering = show } -// Set flag to indicate whether or not to include in the table of contents. -func (subchap *subchapter) SetIncludeInTOC(includeInTOC bool) { +// SetIncludeInTOC sets a flag to indicate whether or not to include in the table of contents. +func (subchap *Subchapter) SetIncludeInTOC(includeInTOC bool) { subchap.includeInTOC = includeInTOC } -// Get access to the heading paragraph to address style etc. -func (subchap *subchapter) GetHeading() *paragraph { +// GetHeading returns the Subchapter's heading Paragraph to address style (font type, size, etc). +func (subchap *Subchapter) GetHeading() *Paragraph { return subchap.heading } @@ -102,34 +104,36 @@ func (subchap *subchapter) SetPos(x, y float64) { } */ -// Set chapter Margins. Typically not needed as the Page Margins are used. -func (subchap *subchapter) SetMargins(left, right, top, bottom float64) { +// SetMargins sets the Subchapter's margins (left, right, top, bottom). +// These margins are typically not needed as the Creator's page margins are used preferably. +func (subchap *Subchapter) SetMargins(left, right, top, bottom float64) { subchap.margins.left = left subchap.margins.right = right subchap.margins.top = top subchap.margins.bottom = bottom } -// Get the subchapter Margins: left, right, top, bototm. -func (subchap *subchapter) GetMargins() (float64, float64, float64, float64) { +// GetMargins returns the Subchapter's margins: left, right, top, bottom. +func (subchap *Subchapter) GetMargins() (float64, float64, float64, float64) { return subchap.margins.left, subchap.margins.right, subchap.margins.top, subchap.margins.bottom } -// Add a new drawable to the chapter. -func (subchap *subchapter) Add(d Drawable) { +// Add adds a new Drawable to the chapter. +// The currently supported Drawables are: *Paragraph, *Image, *Block, *Table. +func (subchap *Subchapter) Add(d Drawable) { switch d.(type) { - case *Chapter, *subchapter: + case *Chapter, *Subchapter: common.Log.Debug("Error: Cannot add chapter or subchapter to a subchapter") - case *paragraph, *image, *Block, *Table: + case *Paragraph, *Image, *Block, *Table: subchap.contents = append(subchap.contents, d) default: common.Log.Debug("Unsupported: %T", d) } } -// Generate the Page blocks. Multiple blocks are generated if the contents wrap over -// multiple pages. -func (subchap *subchapter) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { +// GeneratePageBlocks generates the page blocks. Multiple blocks are generated if the contents wrap over +// multiple pages. Implements the Drawable interface. +func (subchap *Subchapter) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { origCtx := ctx if subchap.positioning.isRelative() { diff --git a/pdf/creator/table.go b/pdf/creator/table.go index 1a1d0371..a38d006b 100644 --- a/pdf/creator/table.go +++ b/pdf/creator/table.go @@ -33,7 +33,7 @@ type Table struct { defaultRowHeight float64 // Content cells. - cells []*tableCell + cells []*TableCell // Positioning: relative / absolute. positioning positioning @@ -45,7 +45,7 @@ type Table struct { margins margins } -// Create a new table with a fixed rows and column size. +// NewTable create a new Table with a specified number of columns. func NewTable(cols int) *Table { t := &Table{} t.rows = 0 @@ -66,13 +66,14 @@ func NewTable(cols int) *Table { // XXX/TODO: Base on contents instead? t.defaultRowHeight = 10.0 - t.cells = []*tableCell{} + t.cells = []*TableCell{} return t } -// Set the fractional column widths. Number of width inputs must match number of columns. +// SetColumnWidths sets the fractional column widths. // Each width should be in the range 0-1 and is a fraction of the table width. +// The number of width inputs must match number of columns, otherwise an error is returned. func (table *Table) SetColumnWidths(widths ...float64) error { if len(widths) != table.cols { common.Log.Debug("Mismatching number of widths and columns") @@ -84,7 +85,7 @@ func (table *Table) SetColumnWidths(widths ...float64) error { return nil } -// Total height of all rows. +// Height returns the total height of all rows. func (table *Table) Height() float64 { sum := float64(0.0) for _, h := range table.rowHeights { @@ -94,7 +95,7 @@ func (table *Table) Height() float64 { return sum } -// Set the left, right, top, bottom Margins. +// SetMargins sets the Table's left, right, top, bottom margins. func (table *Table) SetMargins(left, right, top, bottom float64) { table.margins.left = left table.margins.right = right @@ -102,12 +103,12 @@ func (table *Table) SetMargins(left, right, top, bottom float64) { table.margins.bottom = bottom } -// Get the left, right, top, bottom Margins. +// GetMargins returns the left, right, top, bottom Margins. func (table *Table) GetMargins() (float64, float64, float64, float64) { return table.margins.left, table.margins.right, table.margins.top, table.margins.bottom } -// Set the height for a specified row. +// SetRowHeight sets the height for a specified row. func (table *Table) SetRowHeight(row int, h float64) error { if row < 1 || row > len(table.rowHeights) { return errors.New("Range check error") @@ -117,30 +118,30 @@ func (table *Table) SetRowHeight(row int, h float64) error { return nil } -// Get row of the current cell position. +// CurRow returns the currently active cell's row number. func (table *Table) CurRow() int { curRow := (table.curCell-1)/table.cols + 1 return curRow } -// Get column of the current cell position. +// CurCol returns the currently active cell's column number. func (table *Table) CurCol() int { curCol := (table.curCell-1)%(table.cols) + 1 return curCol } -// Set absolute coordinates. +// SetPos sets the Table's positioning to absolute mode and specifies the upper-left corner coordinates as (x,y). // Note that this is only sensible to use when the table does not wrap over multiple pages. -// XXX/TODO: Should be able to set width too (not just based on context/relative positioning mode). +// TODO: Should be able to set width too (not just based on context/relative positioning mode). func (table *Table) SetPos(x, y float64) { table.positioning = positionAbsolute table.xPos = x table.yPos = y } -// Generate the Page blocks. Multiple blocks are generated if the contents wrap over multiple pages. +// GeneratePageBlocks generate the page blocks. Multiple blocks are generated if the contents wrap over multiple pages. +// Implements the Drawable interface. func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) { - blocks := []*Block{} block := NewBlock(ctx.PageWidth, ctx.PageHeight) @@ -311,7 +312,7 @@ func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, return blocks, ctx, nil } -// Define table cell's border style. +// CellBorderStyle defines the table cell's border style. type CellBorderStyle int // Currently supported table styles are: None (no border) and boxed (line along each side). @@ -323,9 +324,10 @@ const ( CellBorderStyleBox ) -// Define table cell's horizontal alignment. +// CellHorizontalAlignment defines the table cell's horizontal alignment. type CellHorizontalAlignment int +// Table cells have three horizontal alignment modes: left, center and right. const ( // Align cell content on the left (with specified indent); unused space on the right. CellHorizontalAlignmentLeft CellHorizontalAlignment = iota @@ -337,9 +339,10 @@ const ( CellHorizontalAlignmentRight ) -// Define table cell's vertical alignment. +// CellVerticalAlignment defines the table cell's vertical alignment. type CellVerticalAlignment int +// Table cells have three vertical alignment modes: top, middle and bottom. const ( // Align cell content vertically to the top; unused space below. CellVerticalAlignmentTop CellVerticalAlignment = iota @@ -351,8 +354,8 @@ const ( CellVerticalAlignmentBottom ) -// Table cell -type tableCell struct { +// TableCell defines a table cell which can contain a Drawable as content. +type TableCell struct { // Background backgroundColor *model.PdfColorDeviceRGB @@ -382,8 +385,8 @@ type tableCell struct { table *Table } -// Make a new cell and insert into the table at current position in the table. -func (table *Table) NewCell() *tableCell { +// NewCell makes a new cell and inserts into the table at current position in the table. +func (table *Table) NewCell() *TableCell { table.curCell++ curRow := (table.curCell-1)/table.cols + 1 @@ -393,7 +396,7 @@ func (table *Table) NewCell() *tableCell { } curCol := (table.curCell-1)%(table.cols) + 1 - cell := &tableCell{} + cell := &TableCell{} cell.row = curRow cell.col = curCol @@ -418,7 +421,7 @@ func (table *Table) NewCell() *tableCell { return cell } -// Skip over a specified number of cells. +// SkipCells skips over a specified number of cells in the table. func (table *Table) SkipCells(num int) { if num < 0 { common.Log.Debug("Table: cannot skip back to previous cells") @@ -427,7 +430,7 @@ func (table *Table) SkipCells(num int) { table.curCell += num } -// Skip over a specified number of rows. +// SkipRows skips over a specified number of rows in the table. func (table *Table) SkipRows(num int) { ncells := num*table.cols - 1 if ncells < 0 { @@ -437,7 +440,7 @@ func (table *Table) SkipRows(num int) { table.curCell += ncells } -// Skip over rows, cols. +// SkipOver skips over a specified number of rows and cols. func (table *Table) SkipOver(rows, cols int) { ncells := rows*table.cols + cols - 1 if ncells < 0 { @@ -447,47 +450,47 @@ func (table *Table) SkipOver(rows, cols int) { table.curCell += ncells } -// Set cell's left indent. -func (cell *tableCell) SetIndent(indent float64) { +// SetIndent sets the cell's left indent. +func (cell *TableCell) SetIndent(indent float64) { cell.indent = indent } -// Set cell's horizontal alignment of content. +// SetHorizontalAlignment sets the cell's horizontal alignment of content. // Can be one of: // - CellHorizontalAlignmentLeft // - CellHorizontalAlignmentCenter // - CellHorizontalAlignmentRight -func (cell *tableCell) SetHorizontalAlignment(halign CellHorizontalAlignment) { +func (cell *TableCell) SetHorizontalAlignment(halign CellHorizontalAlignment) { cell.horizontalAlignment = halign } -// Set cell's vertical alignment of content. +// SetVerticalAlignment set the cell's vertical alignment of content. // Can be one of: // - CellHorizontalAlignmentTop // - CellHorizontalAlignmentMiddle // - CellHorizontalAlignmentBottom -func (cell *tableCell) SetVerticalAlignment(valign CellVerticalAlignment) { +func (cell *TableCell) SetVerticalAlignment(valign CellVerticalAlignment) { cell.verticalAlignment = valign } -// Set cell's border style. -func (cell *tableCell) SetBorder(style CellBorderStyle, width float64) { +// SetBorder sets the cell's border style. +func (cell *TableCell) SetBorder(style CellBorderStyle, width float64) { cell.borderStyle = style cell.borderWidth = width } -// Set border color. -func (cell *tableCell) SetBorderColor(color rgbColor) { +// SetBorderColor sets the cell's border color. +func (cell *TableCell) SetBorderColor(color rgbColor) { cell.borderColor = model.NewPdfColorDeviceRGB(color.r, color.g, color.b) } -// Set cell's background color. -func (cell *tableCell) SetBackgroundColor(col color) { +// SetBackgroundColor sets the cell's background color. +func (cell *TableCell) SetBackgroundColor(col Color) { cell.backgroundColor = model.NewPdfColorDeviceRGB(col.ToRGB()) } -// Get cell width based on input draw context. -func (cell *tableCell) Width(ctx DrawContext) float64 { +// Width returns the cell's width based on the input draw context. +func (cell *TableCell) Width(ctx DrawContext) float64 { fraction := float64(0.0) for j := 0; j < cell.colspan; j++ { fraction += cell.table.colWidths[cell.col+j-1] @@ -496,10 +499,12 @@ func (cell *tableCell) Width(ctx DrawContext) float64 { return w } -// Set cell content. The content is a vector drawable, i.e. a drawable with a known height and width. -func (cell *tableCell) SetContent(vd VectorDrawable) error { +// SetContent sets the cell's content. The content is a VectorDrawable, i.e. a Drawable with a known height and width. +// The currently supported VectorDrawable is: *Paragraph. +// TODO: Add support for *Image, *Block. +func (cell *TableCell) SetContent(vd VectorDrawable) error { switch t := vd.(type) { - case *paragraph: + case *Paragraph: // Default paragraph settings in table: t.SetEnableWrap(false) // No wrapping. h := cell.table.rowHeights[cell.row-1] diff --git a/pdf/creator/toc.go b/pdf/creator/toc.go index 4c8390a7..108ee460 100644 --- a/pdf/creator/toc.go +++ b/pdf/creator/toc.go @@ -5,26 +5,26 @@ package creator -// The table of contents has overview over chapters and subchapters. +// TableOfContents provides an overview over chapters and subchapters when creating a document with Creator. type TableOfContents struct { - entries []tableOfContentsEntry + entries []TableOfContentsEntry } // Make a new table of contents. func newTableOfContents() *TableOfContents { toc := TableOfContents{} - toc.entries = []tableOfContentsEntry{} + toc.entries = []TableOfContentsEntry{} return &toc } -// Get table of content entries. -func (toc *TableOfContents) Entries() []tableOfContentsEntry { +// Entries returns the table of content entries. +func (toc *TableOfContents) Entries() []TableOfContentsEntry { return toc.entries } // Add a TOC entry. func (toc *TableOfContents) add(title string, chapter, subchapter, pageNum int) { - entry := tableOfContentsEntry{} + entry := TableOfContentsEntry{} entry.Title = title entry.Chapter = chapter entry.Subchapter = subchapter @@ -33,8 +33,9 @@ func (toc *TableOfContents) add(title string, chapter, subchapter, pageNum int) toc.entries = append(toc.entries, entry) } -// Each TOC entry has title, chapter number, sub chapter (0 if chapter) and the page number. -type tableOfContentsEntry struct { +// TableOfContentsEntry defines a single entry in the TableOfContents. +// Each entry has a title, chapter number, sub chapter (0 if chapter) and the page number. +type TableOfContentsEntry struct { Title string Chapter int Subchapter int // 0 if chapter diff --git a/pdf/image.go b/pdf/image.go deleted file mode 100644 index c17cc4c2..00000000 --- a/pdf/image.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - * This file is subject to the terms and conditions defined in - * file 'LICENSE.md', which is part of this source code package. - */ - -package pdf - -import ( - "bytes" - "image" - "image/draw" - _ "image/gif" - "image/jpeg" - _ "image/png" - "io" - - "github.com/unidoc/unidoc/common" -) - -// Basic representation of an image. The images are stored in the JPEG -// format. -type Image struct { - Width int64 - Height int64 - Data *bytes.Buffer -} - -type ImageHandler interface { - // Read any image type and load into a new Image object. - Read(r io.Reader) (*Image, error) - // Compress an image. - Compress(input *Image, quality int64) (*Image, error) -} - -// Default implementation. - -type DefaultImageHandler struct{} - -func (this DefaultImageHandler) Read(reader io.Reader) (*Image, error) { - // Load the image with the native implementation. - img, _, err := image.Decode(reader) - if err != nil { - common.Log.Error("Error decoding file: %s", err) - return nil, err - } - - // Write image stream. - var buf bytes.Buffer - opt := jpeg.Options{} - // Use full quality. - opt.Quality = 100 - - // Speed up jpeg encoding by converting to RGBA first. - // Will not be required once the golang image/jpeg package is optimized. - b := img.Bounds() - m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy())) - draw.Draw(m, m.Bounds(), img, b.Min, draw.Src) - err = jpeg.Encode(&buf, m, &opt) - if err != nil { - return nil, err - } - - imag := Image{} - imag.Width = int64(b.Dx()) - imag.Height = int64(b.Dy()) - imag.Data = &buf - - return &imag, nil -} - -// To be implemented. -func (this DefaultImageHandler) Compress(input *Image, quality int64) (*Image, error) { - return input, nil -} - -var ImageHandling ImageHandler = DefaultImageHandler{} - -func SetImageHandler(imgHandling ImageHandler) { - ImageHandling = imgHandling -} diff --git a/pdf/model/annotations.go b/pdf/model/annotations.go index 5c3b9a0b..a94b0080 100644 --- a/pdf/model/annotations.go +++ b/pdf/model/annotations.go @@ -938,18 +938,18 @@ func (r *PdfReader) newPdfAnnotationMarkupFromDict(d *PdfObjectDictionary) (*Pdf if _, isNull := obj.(*PdfObjectNull); !isNull { return nil, fmt.Errorf("Popup should point to an indirect object") } - } + } else { + popupAnnotObj, err := r.newPdfAnnotationFromIndirectObject(indObj) + if err != nil { + return nil, err + } + popupAnnot, isPopupAnnot := popupAnnotObj.context.(*PdfAnnotationPopup) + if !isPopupAnnot { + return nil, fmt.Errorf("Popup not referring to a popup annotation!") + } - popupAnnotObj, err := r.newPdfAnnotationFromIndirectObject(indObj) - if err != nil { - return nil, err + annot.Popup = popupAnnot } - popupAnnot, isPopupAnnot := popupAnnotObj.context.(*PdfAnnotationPopup) - if !isPopupAnnot { - return nil, fmt.Errorf("Popup not referring to a popup annotation!") - } - - annot.Popup = popupAnnot } if obj := d.Get("CA"); obj != nil { diff --git a/pdf/model/colorspace.go b/pdf/model/colorspace.go index 29c19603..2af2e279 100644 --- a/pdf/model/colorspace.go +++ b/pdf/model/colorspace.go @@ -138,6 +138,55 @@ func newPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) { return nil, errors.New("Type error") } +// determine PDF colorspace from a PdfObject. Returns the colorspace name and an error on failure. +// If the colorspace was not found, will return an empty string. +func determineColorspaceNameFromPdfObject(obj PdfObject) (PdfObjectName, error) { + var csName *PdfObjectName + var csArray *PdfObjectArray + + if indObj, is := obj.(*PdfIndirectObject); is { + if array, is := indObj.PdfObject.(*PdfObjectArray); is { + csArray = array + } else if name, is := indObj.PdfObject.(*PdfObjectName); is { + csName = name + } + } else if array, is := obj.(*PdfObjectArray); is { + csArray = array + } else if name, is := obj.(*PdfObjectName); is { + csName = name + } + + // If specified by a name directly: Device colorspace or Pattern. + if csName != nil { + switch *csName { + case "DeviceGray", "DeviceRGB", "DeviceCMYK": + return *csName, nil + case "Pattern": + return *csName, nil + } + } + + if csArray != nil && len(*csArray) > 0 { + if name, is := (*csArray)[0].(*PdfObjectName); is { + switch *name { + case "DeviceGray", "DeviceRGB", "DeviceCMYK": + if len(*csArray) == 1 { + return *name, nil + } + case "CalGray", "CalRGB", "Lab": + return *name, nil + case "ICCBased", "Pattern", "Indexed": + return *name, nil + case "Separation", "DeviceN": + return *name, nil + } + } + } + + // Not found + return "", nil +} + // Gray scale component. // No specific parameters @@ -544,7 +593,7 @@ func (this *PdfColorspaceDeviceCMYK) ImageToRGB(img Image) (Image, error) { decode = []float64{0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0} } if len(decode) != 8 { - common.Log.Debug("Invalid decode array (%d): % d", len(decode), decode) + common.Log.Debug("Invalid decode array (%d): % .3f", len(decode), decode) return img, errors.New("Invalid decode array") } common.Log.Trace("Decode array: % f", decode) @@ -763,7 +812,7 @@ func (this *PdfColorspaceCalGray) ColorFromFloats(vals []float64) (PdfColor, err } func (this *PdfColorspaceCalGray) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { - if len(objects) != 4 { + if len(objects) != 1 { return nil, errors.New("Range check") } @@ -1367,19 +1416,34 @@ func (this *PdfColorspaceLab) ColorFromFloats(vals []float64) (PdfColor, error) // L l := vals[0] - if l < 0.0 || l > 1.0 { + if l < 0.0 || l > 100.0 { + common.Log.Debug("L out of range (got %v should be 0-100)", l) return nil, errors.New("Range check") } // A a := vals[1] - if a < 0.0 || a > 1.0 { + aMin := float64(-100) + aMax := float64(100) + if len(this.Range) > 1 { + aMin = this.Range[0] + aMax = this.Range[1] + } + if a < aMin || a > aMax { + common.Log.Debug("A out of range (got %v; range %v to %v)", a, aMin, aMax) return nil, errors.New("Range check") } // B. b := vals[2] - if b < 0.0 || b > 1.0 { + bMin := float64(-100) + bMax := float64(100) + if len(this.Range) > 3 { + bMin = this.Range[2] + bMax = this.Range[3] + } + if b < bMin || b > bMax { + common.Log.Debug("b out of range (got %v; range %v to %v)", b, bMin, bMax) return nil, errors.New("Range check") } @@ -1416,6 +1480,8 @@ func (this *PdfColorspaceLab) ColorToRGB(color PdfColor) (PdfColor, error) { } // Get normalized L*, a*, b* values. [0-1] + // XXX/FIXME: According to the pdf standard these values are going to be in range 0-100, -100 - 100, -100 - 100 + // by default. LNorm := lab.L() ANorm := lab.A() BNorm := lab.B() @@ -1937,7 +2003,6 @@ func (this *PdfColorspaceSpecialPattern) ColorFromFloats(vals []float64) (PdfCol // the name of the pattern. func (this *PdfColorspaceSpecialPattern) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { if len(objects) < 1 { - common.Log.Error("ColorFromPdfObjects: len(objects)=%d", len(objects)) return nil, errors.New("Invalid number of parameters") } patternColor := &PdfColorPattern{} @@ -2053,6 +2118,14 @@ func newPdfColorspaceSpecialIndexedFromPdfObject(obj PdfObject) (*PdfColorspaceS // Get base colormap. obj = (*array)[1] + + // Base cs cannot be another /Indexed or /Pattern space. + baseName, err := determineColorspaceNameFromPdfObject(obj) + if baseName == "Indexed" || baseName == "Pattern" { + common.Log.Debug("Error: Indexed colorspace cannot have Indexed/Pattern CS as base (%v)", baseName) + return nil, ErrRangeError + } + baseCs, err := newPdfColorspaceFromPdfObject(obj) if err != nil { return nil, err @@ -2154,7 +2227,15 @@ func (this *PdfColorspaceSpecialIndexed) ColorToRGB(color PdfColor) (PdfColor, e // Convert an indexed image to RGB. func (this *PdfColorspaceSpecialIndexed) ImageToRGB(img Image) (Image, error) { - baseImage := img + //baseImage := img + // Make a new representation of the image to be converted with the base colorspace. + baseImage := Image{} + baseImage.Height = img.Height + baseImage.Width = img.Width + baseImage.alphaData = img.alphaData + baseImage.BitsPerComponent = img.BitsPerComponent + baseImage.hasAlpha = img.hasAlpha + baseImage.ColorComponents = img.ColorComponents samples := img.GetSamples() N := this.Base.GetNumComponents() @@ -2318,11 +2399,15 @@ func (this *PdfColorspaceSpecialSeparation) ColorFromFloats(vals []float64) (Pdf input := []float64{tint} output, err := this.TintTransform.Evaluate(input) if err != nil { + common.Log.Debug("Error, failed to evaluate: %v", err) + common.Log.Trace("Tint transform: %+v", this.TintTransform) return nil, err } + common.Log.Trace("Processing ColorFromFloats(%+v) on AlternateSpace: %#v", output, this.AlternateSpace) color, err := this.AlternateSpace.ColorFromFloats(output) if err != nil { + common.Log.Debug("Error, failed to evaluate in alternate space: %v", err) return nil, err } @@ -2416,6 +2501,7 @@ func (this *PdfColorspaceDeviceN) String() string { return "DeviceN" } +// GetNumComponents returns the number of input color components, i.e. that are input to the tint transform. func (this *PdfColorspaceDeviceN) GetNumComponents() int { return len(*this.ColorantNames) } diff --git a/pdf/model/fonts/afms/export_metrics.go b/pdf/model/fonts/afms/export_metrics.go index d5be98e9..5760b402 100644 --- a/pdf/model/fonts/afms/export_metrics.go +++ b/pdf/model/fonts/afms/export_metrics.go @@ -1,3 +1,5 @@ +// +build unidev + // Parse character metrics from an AFM file to convert into a static go code declaration. package main @@ -56,7 +58,7 @@ func runCharmetricsOnFile(path string) error { } keys := []string{} - for key, _ := range metrics { + for key := range metrics { keys = append(keys, key) } @@ -77,7 +79,7 @@ func runCharcodeToGlyphRetrievalOnFile(afmpath string) error { } keys := []int{} - for key, _ := range charcodeToGlyphMap { + for key := range charcodeToGlyphMap { keys = append(keys, int(key)) } sort.Ints(keys) @@ -98,7 +100,7 @@ func runGlyphToCharcodeRetrievalOnFile(afmpath string) error { } keys := []int{} - for key, _ := range charcodeToGlyphMap { + for key := range charcodeToGlyphMap { keys = append(keys, int(key)) } sort.Ints(keys) diff --git a/pdf/model/fonts/helvetica.go b/pdf/model/fonts/helvetica.go index 18ac7ca3..e2cbfb26 100644 --- a/pdf/model/fonts/helvetica.go +++ b/pdf/model/fonts/helvetica.go @@ -54,319 +54,319 @@ func (font fontHelvetica) ToPdfObject() core.PdfObject { // Helvetica font metics loaded from afms/Helvetica.afm. See afms/MustRead.html for license information. var helveticaCharMetrics map[string]CharMetrics = map[string]CharMetrics{ - "A": CharMetrics{GlyphName: "A", Wx: 667.000000, Wy: 0.000000}, - "AE": CharMetrics{GlyphName: "AE", Wx: 1000.000000, Wy: 0.000000}, - "Aacute": CharMetrics{GlyphName: "Aacute", Wx: 667.000000, Wy: 0.000000}, - "Abreve": CharMetrics{GlyphName: "Abreve", Wx: 667.000000, Wy: 0.000000}, - "Acircumflex": CharMetrics{GlyphName: "Acircumflex", Wx: 667.000000, Wy: 0.000000}, - "Adieresis": CharMetrics{GlyphName: "Adieresis", Wx: 667.000000, Wy: 0.000000}, - "Agrave": CharMetrics{GlyphName: "Agrave", Wx: 667.000000, Wy: 0.000000}, - "Amacron": CharMetrics{GlyphName: "Amacron", Wx: 667.000000, Wy: 0.000000}, - "Aogonek": CharMetrics{GlyphName: "Aogonek", Wx: 667.000000, Wy: 0.000000}, - "Aring": CharMetrics{GlyphName: "Aring", Wx: 667.000000, Wy: 0.000000}, - "Atilde": CharMetrics{GlyphName: "Atilde", Wx: 667.000000, Wy: 0.000000}, - "B": CharMetrics{GlyphName: "B", Wx: 667.000000, Wy: 0.000000}, - "C": CharMetrics{GlyphName: "C", Wx: 722.000000, Wy: 0.000000}, - "Cacute": CharMetrics{GlyphName: "Cacute", Wx: 722.000000, Wy: 0.000000}, - "Ccaron": CharMetrics{GlyphName: "Ccaron", Wx: 722.000000, Wy: 0.000000}, - "Ccedilla": CharMetrics{GlyphName: "Ccedilla", Wx: 722.000000, Wy: 0.000000}, - "D": CharMetrics{GlyphName: "D", Wx: 722.000000, Wy: 0.000000}, - "Dcaron": CharMetrics{GlyphName: "Dcaron", Wx: 722.000000, Wy: 0.000000}, - "Dcroat": CharMetrics{GlyphName: "Dcroat", Wx: 722.000000, Wy: 0.000000}, - "Delta": CharMetrics{GlyphName: "Delta", Wx: 612.000000, Wy: 0.000000}, - "E": CharMetrics{GlyphName: "E", Wx: 667.000000, Wy: 0.000000}, - "Eacute": CharMetrics{GlyphName: "Eacute", Wx: 667.000000, Wy: 0.000000}, - "Ecaron": CharMetrics{GlyphName: "Ecaron", Wx: 667.000000, Wy: 0.000000}, - "Ecircumflex": CharMetrics{GlyphName: "Ecircumflex", Wx: 667.000000, Wy: 0.000000}, - "Edieresis": CharMetrics{GlyphName: "Edieresis", Wx: 667.000000, Wy: 0.000000}, - "Edotaccent": CharMetrics{GlyphName: "Edotaccent", Wx: 667.000000, Wy: 0.000000}, - "Egrave": CharMetrics{GlyphName: "Egrave", Wx: 667.000000, Wy: 0.000000}, - "Emacron": CharMetrics{GlyphName: "Emacron", Wx: 667.000000, Wy: 0.000000}, - "Eogonek": CharMetrics{GlyphName: "Eogonek", Wx: 667.000000, Wy: 0.000000}, - "Eth": CharMetrics{GlyphName: "Eth", Wx: 722.000000, Wy: 0.000000}, - "Euro": CharMetrics{GlyphName: "Euro", Wx: 556.000000, Wy: 0.000000}, - "F": CharMetrics{GlyphName: "F", Wx: 611.000000, Wy: 0.000000}, - "G": CharMetrics{GlyphName: "G", Wx: 778.000000, Wy: 0.000000}, - "Gbreve": CharMetrics{GlyphName: "Gbreve", Wx: 778.000000, Wy: 0.000000}, - "Gcommaaccent": CharMetrics{GlyphName: "Gcommaaccent", Wx: 778.000000, Wy: 0.000000}, - "H": CharMetrics{GlyphName: "H", Wx: 722.000000, Wy: 0.000000}, - "I": CharMetrics{GlyphName: "I", Wx: 278.000000, Wy: 0.000000}, - "Iacute": CharMetrics{GlyphName: "Iacute", Wx: 278.000000, Wy: 0.000000}, - "Icircumflex": CharMetrics{GlyphName: "Icircumflex", Wx: 278.000000, Wy: 0.000000}, - "Idieresis": CharMetrics{GlyphName: "Idieresis", Wx: 278.000000, Wy: 0.000000}, - "Idotaccent": CharMetrics{GlyphName: "Idotaccent", Wx: 278.000000, Wy: 0.000000}, - "Igrave": CharMetrics{GlyphName: "Igrave", Wx: 278.000000, Wy: 0.000000}, - "Imacron": CharMetrics{GlyphName: "Imacron", Wx: 278.000000, Wy: 0.000000}, - "Iogonek": CharMetrics{GlyphName: "Iogonek", Wx: 278.000000, Wy: 0.000000}, - "J": CharMetrics{GlyphName: "J", Wx: 500.000000, Wy: 0.000000}, - "K": CharMetrics{GlyphName: "K", Wx: 667.000000, Wy: 0.000000}, - "Kcommaaccent": CharMetrics{GlyphName: "Kcommaaccent", Wx: 667.000000, Wy: 0.000000}, - "L": CharMetrics{GlyphName: "L", Wx: 556.000000, Wy: 0.000000}, - "Lacute": CharMetrics{GlyphName: "Lacute", Wx: 556.000000, Wy: 0.000000}, - "Lcaron": CharMetrics{GlyphName: "Lcaron", Wx: 556.000000, Wy: 0.000000}, - "Lcommaaccent": CharMetrics{GlyphName: "Lcommaaccent", Wx: 556.000000, Wy: 0.000000}, - "Lslash": CharMetrics{GlyphName: "Lslash", Wx: 556.000000, Wy: 0.000000}, - "M": CharMetrics{GlyphName: "M", Wx: 833.000000, Wy: 0.000000}, - "N": CharMetrics{GlyphName: "N", Wx: 722.000000, Wy: 0.000000}, - "Nacute": CharMetrics{GlyphName: "Nacute", Wx: 722.000000, Wy: 0.000000}, - "Ncaron": CharMetrics{GlyphName: "Ncaron", Wx: 722.000000, Wy: 0.000000}, - "Ncommaaccent": CharMetrics{GlyphName: "Ncommaaccent", Wx: 722.000000, Wy: 0.000000}, - "Ntilde": CharMetrics{GlyphName: "Ntilde", Wx: 722.000000, Wy: 0.000000}, - "O": CharMetrics{GlyphName: "O", Wx: 778.000000, Wy: 0.000000}, - "OE": CharMetrics{GlyphName: "OE", Wx: 1000.000000, Wy: 0.000000}, - "Oacute": CharMetrics{GlyphName: "Oacute", Wx: 778.000000, Wy: 0.000000}, - "Ocircumflex": CharMetrics{GlyphName: "Ocircumflex", Wx: 778.000000, Wy: 0.000000}, - "Odieresis": CharMetrics{GlyphName: "Odieresis", Wx: 778.000000, Wy: 0.000000}, - "Ograve": CharMetrics{GlyphName: "Ograve", Wx: 778.000000, Wy: 0.000000}, - "Ohungarumlaut": CharMetrics{GlyphName: "Ohungarumlaut", Wx: 778.000000, Wy: 0.000000}, - "Omacron": CharMetrics{GlyphName: "Omacron", Wx: 778.000000, Wy: 0.000000}, - "Oslash": CharMetrics{GlyphName: "Oslash", Wx: 778.000000, Wy: 0.000000}, - "Otilde": CharMetrics{GlyphName: "Otilde", Wx: 778.000000, Wy: 0.000000}, - "P": CharMetrics{GlyphName: "P", Wx: 667.000000, Wy: 0.000000}, - "Q": CharMetrics{GlyphName: "Q", Wx: 778.000000, Wy: 0.000000}, - "R": CharMetrics{GlyphName: "R", Wx: 722.000000, Wy: 0.000000}, - "Racute": CharMetrics{GlyphName: "Racute", Wx: 722.000000, Wy: 0.000000}, - "Rcaron": CharMetrics{GlyphName: "Rcaron", Wx: 722.000000, Wy: 0.000000}, - "Rcommaaccent": CharMetrics{GlyphName: "Rcommaaccent", Wx: 722.000000, Wy: 0.000000}, - "S": CharMetrics{GlyphName: "S", Wx: 667.000000, Wy: 0.000000}, - "Sacute": CharMetrics{GlyphName: "Sacute", Wx: 667.000000, Wy: 0.000000}, - "Scaron": CharMetrics{GlyphName: "Scaron", Wx: 667.000000, Wy: 0.000000}, - "Scedilla": CharMetrics{GlyphName: "Scedilla", Wx: 667.000000, Wy: 0.000000}, - "Scommaaccent": CharMetrics{GlyphName: "Scommaaccent", Wx: 667.000000, Wy: 0.000000}, - "T": CharMetrics{GlyphName: "T", Wx: 611.000000, Wy: 0.000000}, - "Tcaron": CharMetrics{GlyphName: "Tcaron", Wx: 611.000000, Wy: 0.000000}, - "Tcommaaccent": CharMetrics{GlyphName: "Tcommaaccent", Wx: 611.000000, Wy: 0.000000}, - "Thorn": CharMetrics{GlyphName: "Thorn", Wx: 667.000000, Wy: 0.000000}, - "U": CharMetrics{GlyphName: "U", Wx: 722.000000, Wy: 0.000000}, - "Uacute": CharMetrics{GlyphName: "Uacute", Wx: 722.000000, Wy: 0.000000}, - "Ucircumflex": CharMetrics{GlyphName: "Ucircumflex", Wx: 722.000000, Wy: 0.000000}, - "Udieresis": CharMetrics{GlyphName: "Udieresis", Wx: 722.000000, Wy: 0.000000}, - "Ugrave": CharMetrics{GlyphName: "Ugrave", Wx: 722.000000, Wy: 0.000000}, - "Uhungarumlaut": CharMetrics{GlyphName: "Uhungarumlaut", Wx: 722.000000, Wy: 0.000000}, - "Umacron": CharMetrics{GlyphName: "Umacron", Wx: 722.000000, Wy: 0.000000}, - "Uogonek": CharMetrics{GlyphName: "Uogonek", Wx: 722.000000, Wy: 0.000000}, - "Uring": CharMetrics{GlyphName: "Uring", Wx: 722.000000, Wy: 0.000000}, - "V": CharMetrics{GlyphName: "V", Wx: 667.000000, Wy: 0.000000}, - "W": CharMetrics{GlyphName: "W", Wx: 944.000000, Wy: 0.000000}, - "X": CharMetrics{GlyphName: "X", Wx: 667.000000, Wy: 0.000000}, - "Y": CharMetrics{GlyphName: "Y", Wx: 667.000000, Wy: 0.000000}, - "Yacute": CharMetrics{GlyphName: "Yacute", Wx: 667.000000, Wy: 0.000000}, - "Ydieresis": CharMetrics{GlyphName: "Ydieresis", Wx: 667.000000, Wy: 0.000000}, - "Z": CharMetrics{GlyphName: "Z", Wx: 611.000000, Wy: 0.000000}, - "Zacute": CharMetrics{GlyphName: "Zacute", Wx: 611.000000, Wy: 0.000000}, - "Zcaron": CharMetrics{GlyphName: "Zcaron", Wx: 611.000000, Wy: 0.000000}, - "Zdotaccent": CharMetrics{GlyphName: "Zdotaccent", Wx: 611.000000, Wy: 0.000000}, - "a": CharMetrics{GlyphName: "a", Wx: 556.000000, Wy: 0.000000}, - "aacute": CharMetrics{GlyphName: "aacute", Wx: 556.000000, Wy: 0.000000}, - "abreve": CharMetrics{GlyphName: "abreve", Wx: 556.000000, Wy: 0.000000}, - "acircumflex": CharMetrics{GlyphName: "acircumflex", Wx: 556.000000, Wy: 0.000000}, - "acute": CharMetrics{GlyphName: "acute", Wx: 333.000000, Wy: 0.000000}, - "adieresis": CharMetrics{GlyphName: "adieresis", Wx: 556.000000, Wy: 0.000000}, - "ae": CharMetrics{GlyphName: "ae", Wx: 889.000000, Wy: 0.000000}, - "agrave": CharMetrics{GlyphName: "agrave", Wx: 556.000000, Wy: 0.000000}, - "amacron": CharMetrics{GlyphName: "amacron", Wx: 556.000000, Wy: 0.000000}, - "ampersand": CharMetrics{GlyphName: "ampersand", Wx: 667.000000, Wy: 0.000000}, - "aogonek": CharMetrics{GlyphName: "aogonek", Wx: 556.000000, Wy: 0.000000}, - "aring": CharMetrics{GlyphName: "aring", Wx: 556.000000, Wy: 0.000000}, - "asciicircum": CharMetrics{GlyphName: "asciicircum", Wx: 469.000000, Wy: 0.000000}, - "asciitilde": CharMetrics{GlyphName: "asciitilde", Wx: 584.000000, Wy: 0.000000}, - "asterisk": CharMetrics{GlyphName: "asterisk", Wx: 389.000000, Wy: 0.000000}, - "at": CharMetrics{GlyphName: "at", Wx: 1015.000000, Wy: 0.000000}, - "atilde": CharMetrics{GlyphName: "atilde", Wx: 556.000000, Wy: 0.000000}, - "b": CharMetrics{GlyphName: "b", Wx: 556.000000, Wy: 0.000000}, - "backslash": CharMetrics{GlyphName: "backslash", Wx: 278.000000, Wy: 0.000000}, - "bar": CharMetrics{GlyphName: "bar", Wx: 260.000000, Wy: 0.000000}, - "braceleft": CharMetrics{GlyphName: "braceleft", Wx: 334.000000, Wy: 0.000000}, - "braceright": CharMetrics{GlyphName: "braceright", Wx: 334.000000, Wy: 0.000000}, - "bracketleft": CharMetrics{GlyphName: "bracketleft", Wx: 278.000000, Wy: 0.000000}, - "bracketright": CharMetrics{GlyphName: "bracketright", Wx: 278.000000, Wy: 0.000000}, - "breve": CharMetrics{GlyphName: "breve", Wx: 333.000000, Wy: 0.000000}, - "brokenbar": CharMetrics{GlyphName: "brokenbar", Wx: 260.000000, Wy: 0.000000}, - "bullet": CharMetrics{GlyphName: "bullet", Wx: 350.000000, Wy: 0.000000}, - "c": CharMetrics{GlyphName: "c", Wx: 500.000000, Wy: 0.000000}, - "cacute": CharMetrics{GlyphName: "cacute", Wx: 500.000000, Wy: 0.000000}, - "caron": CharMetrics{GlyphName: "caron", Wx: 333.000000, Wy: 0.000000}, - "ccaron": CharMetrics{GlyphName: "ccaron", Wx: 500.000000, Wy: 0.000000}, - "ccedilla": CharMetrics{GlyphName: "ccedilla", Wx: 500.000000, Wy: 0.000000}, - "cedilla": CharMetrics{GlyphName: "cedilla", Wx: 333.000000, Wy: 0.000000}, - "cent": CharMetrics{GlyphName: "cent", Wx: 556.000000, Wy: 0.000000}, - "circumflex": CharMetrics{GlyphName: "circumflex", Wx: 333.000000, Wy: 0.000000}, - "colon": CharMetrics{GlyphName: "colon", Wx: 278.000000, Wy: 0.000000}, - "comma": CharMetrics{GlyphName: "comma", Wx: 278.000000, Wy: 0.000000}, - "commaaccent": CharMetrics{GlyphName: "commaaccent", Wx: 250.000000, Wy: 0.000000}, - "copyright": CharMetrics{GlyphName: "copyright", Wx: 737.000000, Wy: 0.000000}, - "currency": CharMetrics{GlyphName: "currency", Wx: 556.000000, Wy: 0.000000}, - "d": CharMetrics{GlyphName: "d", Wx: 556.000000, Wy: 0.000000}, - "dagger": CharMetrics{GlyphName: "dagger", Wx: 556.000000, Wy: 0.000000}, - "daggerdbl": CharMetrics{GlyphName: "daggerdbl", Wx: 556.000000, Wy: 0.000000}, - "dcaron": CharMetrics{GlyphName: "dcaron", Wx: 643.000000, Wy: 0.000000}, - "dcroat": CharMetrics{GlyphName: "dcroat", Wx: 556.000000, Wy: 0.000000}, - "degree": CharMetrics{GlyphName: "degree", Wx: 400.000000, Wy: 0.000000}, - "dieresis": CharMetrics{GlyphName: "dieresis", Wx: 333.000000, Wy: 0.000000}, - "divide": CharMetrics{GlyphName: "divide", Wx: 584.000000, Wy: 0.000000}, - "dollar": CharMetrics{GlyphName: "dollar", Wx: 556.000000, Wy: 0.000000}, - "dotaccent": CharMetrics{GlyphName: "dotaccent", Wx: 333.000000, Wy: 0.000000}, - "dotlessi": CharMetrics{GlyphName: "dotlessi", Wx: 278.000000, Wy: 0.000000}, - "e": CharMetrics{GlyphName: "e", Wx: 556.000000, Wy: 0.000000}, - "eacute": CharMetrics{GlyphName: "eacute", Wx: 556.000000, Wy: 0.000000}, - "ecaron": CharMetrics{GlyphName: "ecaron", Wx: 556.000000, Wy: 0.000000}, - "ecircumflex": CharMetrics{GlyphName: "ecircumflex", Wx: 556.000000, Wy: 0.000000}, - "edieresis": CharMetrics{GlyphName: "edieresis", Wx: 556.000000, Wy: 0.000000}, - "edotaccent": CharMetrics{GlyphName: "edotaccent", Wx: 556.000000, Wy: 0.000000}, - "egrave": CharMetrics{GlyphName: "egrave", Wx: 556.000000, Wy: 0.000000}, - "eight": CharMetrics{GlyphName: "eight", Wx: 556.000000, Wy: 0.000000}, - "ellipsis": CharMetrics{GlyphName: "ellipsis", Wx: 1000.000000, Wy: 0.000000}, - "emacron": CharMetrics{GlyphName: "emacron", Wx: 556.000000, Wy: 0.000000}, - "emdash": CharMetrics{GlyphName: "emdash", Wx: 1000.000000, Wy: 0.000000}, - "endash": CharMetrics{GlyphName: "endash", Wx: 556.000000, Wy: 0.000000}, - "eogonek": CharMetrics{GlyphName: "eogonek", Wx: 556.000000, Wy: 0.000000}, - "equal": CharMetrics{GlyphName: "equal", Wx: 584.000000, Wy: 0.000000}, - "eth": CharMetrics{GlyphName: "eth", Wx: 556.000000, Wy: 0.000000}, - "exclam": CharMetrics{GlyphName: "exclam", Wx: 278.000000, Wy: 0.000000}, - "exclamdown": CharMetrics{GlyphName: "exclamdown", Wx: 333.000000, Wy: 0.000000}, - "f": CharMetrics{GlyphName: "f", Wx: 278.000000, Wy: 0.000000}, - "fi": CharMetrics{GlyphName: "fi", Wx: 500.000000, Wy: 0.000000}, - "five": CharMetrics{GlyphName: "five", Wx: 556.000000, Wy: 0.000000}, - "fl": CharMetrics{GlyphName: "fl", Wx: 500.000000, Wy: 0.000000}, - "florin": CharMetrics{GlyphName: "florin", Wx: 556.000000, Wy: 0.000000}, - "four": CharMetrics{GlyphName: "four", Wx: 556.000000, Wy: 0.000000}, - "fraction": CharMetrics{GlyphName: "fraction", Wx: 167.000000, Wy: 0.000000}, - "g": CharMetrics{GlyphName: "g", Wx: 556.000000, Wy: 0.000000}, - "gbreve": CharMetrics{GlyphName: "gbreve", Wx: 556.000000, Wy: 0.000000}, - "gcommaaccent": CharMetrics{GlyphName: "gcommaaccent", Wx: 556.000000, Wy: 0.000000}, - "germandbls": CharMetrics{GlyphName: "germandbls", Wx: 611.000000, Wy: 0.000000}, - "grave": CharMetrics{GlyphName: "grave", Wx: 333.000000, Wy: 0.000000}, - "greater": CharMetrics{GlyphName: "greater", Wx: 584.000000, Wy: 0.000000}, - "greaterequal": CharMetrics{GlyphName: "greaterequal", Wx: 549.000000, Wy: 0.000000}, - "guillemotleft": CharMetrics{GlyphName: "guillemotleft", Wx: 556.000000, Wy: 0.000000}, - "guillemotright": CharMetrics{GlyphName: "guillemotright", Wx: 556.000000, Wy: 0.000000}, - "guilsinglleft": CharMetrics{GlyphName: "guilsinglleft", Wx: 333.000000, Wy: 0.000000}, - "guilsinglright": CharMetrics{GlyphName: "guilsinglright", Wx: 333.000000, Wy: 0.000000}, - "h": CharMetrics{GlyphName: "h", Wx: 556.000000, Wy: 0.000000}, - "hungarumlaut": CharMetrics{GlyphName: "hungarumlaut", Wx: 333.000000, Wy: 0.000000}, - "hyphen": CharMetrics{GlyphName: "hyphen", Wx: 333.000000, Wy: 0.000000}, - "i": CharMetrics{GlyphName: "i", Wx: 222.000000, Wy: 0.000000}, - "iacute": CharMetrics{GlyphName: "iacute", Wx: 278.000000, Wy: 0.000000}, - "icircumflex": CharMetrics{GlyphName: "icircumflex", Wx: 278.000000, Wy: 0.000000}, - "idieresis": CharMetrics{GlyphName: "idieresis", Wx: 278.000000, Wy: 0.000000}, - "igrave": CharMetrics{GlyphName: "igrave", Wx: 278.000000, Wy: 0.000000}, - "imacron": CharMetrics{GlyphName: "imacron", Wx: 278.000000, Wy: 0.000000}, - "iogonek": CharMetrics{GlyphName: "iogonek", Wx: 222.000000, Wy: 0.000000}, - "j": CharMetrics{GlyphName: "j", Wx: 222.000000, Wy: 0.000000}, - "k": CharMetrics{GlyphName: "k", Wx: 500.000000, Wy: 0.000000}, - "kcommaaccent": CharMetrics{GlyphName: "kcommaaccent", Wx: 500.000000, Wy: 0.000000}, - "l": CharMetrics{GlyphName: "l", Wx: 222.000000, Wy: 0.000000}, - "lacute": CharMetrics{GlyphName: "lacute", Wx: 222.000000, Wy: 0.000000}, - "lcaron": CharMetrics{GlyphName: "lcaron", Wx: 299.000000, Wy: 0.000000}, - "lcommaaccent": CharMetrics{GlyphName: "lcommaaccent", Wx: 222.000000, Wy: 0.000000}, - "less": CharMetrics{GlyphName: "less", Wx: 584.000000, Wy: 0.000000}, - "lessequal": CharMetrics{GlyphName: "lessequal", Wx: 549.000000, Wy: 0.000000}, - "logicalnot": CharMetrics{GlyphName: "logicalnot", Wx: 584.000000, Wy: 0.000000}, - "lozenge": CharMetrics{GlyphName: "lozenge", Wx: 471.000000, Wy: 0.000000}, - "lslash": CharMetrics{GlyphName: "lslash", Wx: 222.000000, Wy: 0.000000}, - "m": CharMetrics{GlyphName: "m", Wx: 833.000000, Wy: 0.000000}, - "macron": CharMetrics{GlyphName: "macron", Wx: 333.000000, Wy: 0.000000}, - "minus": CharMetrics{GlyphName: "minus", Wx: 584.000000, Wy: 0.000000}, - "mu": CharMetrics{GlyphName: "mu", Wx: 556.000000, Wy: 0.000000}, - "multiply": CharMetrics{GlyphName: "multiply", Wx: 584.000000, Wy: 0.000000}, - "n": CharMetrics{GlyphName: "n", Wx: 556.000000, Wy: 0.000000}, - "nacute": CharMetrics{GlyphName: "nacute", Wx: 556.000000, Wy: 0.000000}, - "ncaron": CharMetrics{GlyphName: "ncaron", Wx: 556.000000, Wy: 0.000000}, - "ncommaaccent": CharMetrics{GlyphName: "ncommaaccent", Wx: 556.000000, Wy: 0.000000}, - "nine": CharMetrics{GlyphName: "nine", Wx: 556.000000, Wy: 0.000000}, - "notequal": CharMetrics{GlyphName: "notequal", Wx: 549.000000, Wy: 0.000000}, - "ntilde": CharMetrics{GlyphName: "ntilde", Wx: 556.000000, Wy: 0.000000}, - "numbersign": CharMetrics{GlyphName: "numbersign", Wx: 556.000000, Wy: 0.000000}, - "o": CharMetrics{GlyphName: "o", Wx: 556.000000, Wy: 0.000000}, - "oacute": CharMetrics{GlyphName: "oacute", Wx: 556.000000, Wy: 0.000000}, - "ocircumflex": CharMetrics{GlyphName: "ocircumflex", Wx: 556.000000, Wy: 0.000000}, - "odieresis": CharMetrics{GlyphName: "odieresis", Wx: 556.000000, Wy: 0.000000}, - "oe": CharMetrics{GlyphName: "oe", Wx: 944.000000, Wy: 0.000000}, - "ogonek": CharMetrics{GlyphName: "ogonek", Wx: 333.000000, Wy: 0.000000}, - "ograve": CharMetrics{GlyphName: "ograve", Wx: 556.000000, Wy: 0.000000}, - "ohungarumlaut": CharMetrics{GlyphName: "ohungarumlaut", Wx: 556.000000, Wy: 0.000000}, - "omacron": CharMetrics{GlyphName: "omacron", Wx: 556.000000, Wy: 0.000000}, - "one": CharMetrics{GlyphName: "one", Wx: 556.000000, Wy: 0.000000}, - "onehalf": CharMetrics{GlyphName: "onehalf", Wx: 834.000000, Wy: 0.000000}, - "onequarter": CharMetrics{GlyphName: "onequarter", Wx: 834.000000, Wy: 0.000000}, - "onesuperior": CharMetrics{GlyphName: "onesuperior", Wx: 333.000000, Wy: 0.000000}, - "ordfeminine": CharMetrics{GlyphName: "ordfeminine", Wx: 370.000000, Wy: 0.000000}, - "ordmasculine": CharMetrics{GlyphName: "ordmasculine", Wx: 365.000000, Wy: 0.000000}, - "oslash": CharMetrics{GlyphName: "oslash", Wx: 611.000000, Wy: 0.000000}, - "otilde": CharMetrics{GlyphName: "otilde", Wx: 556.000000, Wy: 0.000000}, - "p": CharMetrics{GlyphName: "p", Wx: 556.000000, Wy: 0.000000}, - "paragraph": CharMetrics{GlyphName: "paragraph", Wx: 537.000000, Wy: 0.000000}, - "parenleft": CharMetrics{GlyphName: "parenleft", Wx: 333.000000, Wy: 0.000000}, - "parenright": CharMetrics{GlyphName: "parenright", Wx: 333.000000, Wy: 0.000000}, - "partialdiff": CharMetrics{GlyphName: "partialdiff", Wx: 476.000000, Wy: 0.000000}, - "percent": CharMetrics{GlyphName: "percent", Wx: 889.000000, Wy: 0.000000}, - "period": CharMetrics{GlyphName: "period", Wx: 278.000000, Wy: 0.000000}, - "periodcentered": CharMetrics{GlyphName: "periodcentered", Wx: 278.000000, Wy: 0.000000}, - "perthousand": CharMetrics{GlyphName: "perthousand", Wx: 1000.000000, Wy: 0.000000}, - "plus": CharMetrics{GlyphName: "plus", Wx: 584.000000, Wy: 0.000000}, - "plusminus": CharMetrics{GlyphName: "plusminus", Wx: 584.000000, Wy: 0.000000}, - "q": CharMetrics{GlyphName: "q", Wx: 556.000000, Wy: 0.000000}, - "question": CharMetrics{GlyphName: "question", Wx: 556.000000, Wy: 0.000000}, - "questiondown": CharMetrics{GlyphName: "questiondown", Wx: 611.000000, Wy: 0.000000}, - "quotedbl": CharMetrics{GlyphName: "quotedbl", Wx: 355.000000, Wy: 0.000000}, - "quotedblbase": CharMetrics{GlyphName: "quotedblbase", Wx: 333.000000, Wy: 0.000000}, - "quotedblleft": CharMetrics{GlyphName: "quotedblleft", Wx: 333.000000, Wy: 0.000000}, - "quotedblright": CharMetrics{GlyphName: "quotedblright", Wx: 333.000000, Wy: 0.000000}, - "quoteleft": CharMetrics{GlyphName: "quoteleft", Wx: 222.000000, Wy: 0.000000}, - "quoteright": CharMetrics{GlyphName: "quoteright", Wx: 222.000000, Wy: 0.000000}, - "quotesinglbase": CharMetrics{GlyphName: "quotesinglbase", Wx: 222.000000, Wy: 0.000000}, - "quotesingle": CharMetrics{GlyphName: "quotesingle", Wx: 191.000000, Wy: 0.000000}, - "r": CharMetrics{GlyphName: "r", Wx: 333.000000, Wy: 0.000000}, - "racute": CharMetrics{GlyphName: "racute", Wx: 333.000000, Wy: 0.000000}, - "radical": CharMetrics{GlyphName: "radical", Wx: 453.000000, Wy: 0.000000}, - "rcaron": CharMetrics{GlyphName: "rcaron", Wx: 333.000000, Wy: 0.000000}, - "rcommaaccent": CharMetrics{GlyphName: "rcommaaccent", Wx: 333.000000, Wy: 0.000000}, - "registered": CharMetrics{GlyphName: "registered", Wx: 737.000000, Wy: 0.000000}, - "ring": CharMetrics{GlyphName: "ring", Wx: 333.000000, Wy: 0.000000}, - "s": CharMetrics{GlyphName: "s", Wx: 500.000000, Wy: 0.000000}, - "sacute": CharMetrics{GlyphName: "sacute", Wx: 500.000000, Wy: 0.000000}, - "scaron": CharMetrics{GlyphName: "scaron", Wx: 500.000000, Wy: 0.000000}, - "scedilla": CharMetrics{GlyphName: "scedilla", Wx: 500.000000, Wy: 0.000000}, - "scommaaccent": CharMetrics{GlyphName: "scommaaccent", Wx: 500.000000, Wy: 0.000000}, - "section": CharMetrics{GlyphName: "section", Wx: 556.000000, Wy: 0.000000}, - "semicolon": CharMetrics{GlyphName: "semicolon", Wx: 278.000000, Wy: 0.000000}, - "seven": CharMetrics{GlyphName: "seven", Wx: 556.000000, Wy: 0.000000}, - "six": CharMetrics{GlyphName: "six", Wx: 556.000000, Wy: 0.000000}, - "slash": CharMetrics{GlyphName: "slash", Wx: 278.000000, Wy: 0.000000}, - "space": CharMetrics{GlyphName: "space", Wx: 278.000000, Wy: 0.000000}, - "sterling": CharMetrics{GlyphName: "sterling", Wx: 556.000000, Wy: 0.000000}, - "summation": CharMetrics{GlyphName: "summation", Wx: 600.000000, Wy: 0.000000}, - "t": CharMetrics{GlyphName: "t", Wx: 278.000000, Wy: 0.000000}, - "tcaron": CharMetrics{GlyphName: "tcaron", Wx: 317.000000, Wy: 0.000000}, - "tcommaaccent": CharMetrics{GlyphName: "tcommaaccent", Wx: 278.000000, Wy: 0.000000}, - "thorn": CharMetrics{GlyphName: "thorn", Wx: 556.000000, Wy: 0.000000}, - "three": CharMetrics{GlyphName: "three", Wx: 556.000000, Wy: 0.000000}, - "threequarters": CharMetrics{GlyphName: "threequarters", Wx: 834.000000, Wy: 0.000000}, - "threesuperior": CharMetrics{GlyphName: "threesuperior", Wx: 333.000000, Wy: 0.000000}, - "tilde": CharMetrics{GlyphName: "tilde", Wx: 333.000000, Wy: 0.000000}, - "trademark": CharMetrics{GlyphName: "trademark", Wx: 1000.000000, Wy: 0.000000}, - "two": CharMetrics{GlyphName: "two", Wx: 556.000000, Wy: 0.000000}, - "twosuperior": CharMetrics{GlyphName: "twosuperior", Wx: 333.000000, Wy: 0.000000}, - "u": CharMetrics{GlyphName: "u", Wx: 556.000000, Wy: 0.000000}, - "uacute": CharMetrics{GlyphName: "uacute", Wx: 556.000000, Wy: 0.000000}, - "ucircumflex": CharMetrics{GlyphName: "ucircumflex", Wx: 556.000000, Wy: 0.000000}, - "udieresis": CharMetrics{GlyphName: "udieresis", Wx: 556.000000, Wy: 0.000000}, - "ugrave": CharMetrics{GlyphName: "ugrave", Wx: 556.000000, Wy: 0.000000}, - "uhungarumlaut": CharMetrics{GlyphName: "uhungarumlaut", Wx: 556.000000, Wy: 0.000000}, - "umacron": CharMetrics{GlyphName: "umacron", Wx: 556.000000, Wy: 0.000000}, - "underscore": CharMetrics{GlyphName: "underscore", Wx: 556.000000, Wy: 0.000000}, - "uogonek": CharMetrics{GlyphName: "uogonek", Wx: 556.000000, Wy: 0.000000}, - "uring": CharMetrics{GlyphName: "uring", Wx: 556.000000, Wy: 0.000000}, - "v": CharMetrics{GlyphName: "v", Wx: 500.000000, Wy: 0.000000}, - "w": CharMetrics{GlyphName: "w", Wx: 722.000000, Wy: 0.000000}, - "x": CharMetrics{GlyphName: "x", Wx: 500.000000, Wy: 0.000000}, - "y": CharMetrics{GlyphName: "y", Wx: 500.000000, Wy: 0.000000}, - "yacute": CharMetrics{GlyphName: "yacute", Wx: 500.000000, Wy: 0.000000}, - "ydieresis": CharMetrics{GlyphName: "ydieresis", Wx: 500.000000, Wy: 0.000000}, - "yen": CharMetrics{GlyphName: "yen", Wx: 556.000000, Wy: 0.000000}, - "z": CharMetrics{GlyphName: "z", Wx: 500.000000, Wy: 0.000000}, - "zacute": CharMetrics{GlyphName: "zacute", Wx: 500.000000, Wy: 0.000000}, - "zcaron": CharMetrics{GlyphName: "zcaron", Wx: 500.000000, Wy: 0.000000}, - "zdotaccent": CharMetrics{GlyphName: "zdotaccent", Wx: 500.000000, Wy: 0.000000}, - "zero": CharMetrics{GlyphName: "zero", Wx: 556.000000, Wy: 0.000000}, + "A": {GlyphName: "A", Wx: 667.000000, Wy: 0.000000}, + "AE": {GlyphName: "AE", Wx: 1000.000000, Wy: 0.000000}, + "Aacute": {GlyphName: "Aacute", Wx: 667.000000, Wy: 0.000000}, + "Abreve": {GlyphName: "Abreve", Wx: 667.000000, Wy: 0.000000}, + "Acircumflex": {GlyphName: "Acircumflex", Wx: 667.000000, Wy: 0.000000}, + "Adieresis": {GlyphName: "Adieresis", Wx: 667.000000, Wy: 0.000000}, + "Agrave": {GlyphName: "Agrave", Wx: 667.000000, Wy: 0.000000}, + "Amacron": {GlyphName: "Amacron", Wx: 667.000000, Wy: 0.000000}, + "Aogonek": {GlyphName: "Aogonek", Wx: 667.000000, Wy: 0.000000}, + "Aring": {GlyphName: "Aring", Wx: 667.000000, Wy: 0.000000}, + "Atilde": {GlyphName: "Atilde", Wx: 667.000000, Wy: 0.000000}, + "B": {GlyphName: "B", Wx: 667.000000, Wy: 0.000000}, + "C": {GlyphName: "C", Wx: 722.000000, Wy: 0.000000}, + "Cacute": {GlyphName: "Cacute", Wx: 722.000000, Wy: 0.000000}, + "Ccaron": {GlyphName: "Ccaron", Wx: 722.000000, Wy: 0.000000}, + "Ccedilla": {GlyphName: "Ccedilla", Wx: 722.000000, Wy: 0.000000}, + "D": {GlyphName: "D", Wx: 722.000000, Wy: 0.000000}, + "Dcaron": {GlyphName: "Dcaron", Wx: 722.000000, Wy: 0.000000}, + "Dcroat": {GlyphName: "Dcroat", Wx: 722.000000, Wy: 0.000000}, + "Delta": {GlyphName: "Delta", Wx: 612.000000, Wy: 0.000000}, + "E": {GlyphName: "E", Wx: 667.000000, Wy: 0.000000}, + "Eacute": {GlyphName: "Eacute", Wx: 667.000000, Wy: 0.000000}, + "Ecaron": {GlyphName: "Ecaron", Wx: 667.000000, Wy: 0.000000}, + "Ecircumflex": {GlyphName: "Ecircumflex", Wx: 667.000000, Wy: 0.000000}, + "Edieresis": {GlyphName: "Edieresis", Wx: 667.000000, Wy: 0.000000}, + "Edotaccent": {GlyphName: "Edotaccent", Wx: 667.000000, Wy: 0.000000}, + "Egrave": {GlyphName: "Egrave", Wx: 667.000000, Wy: 0.000000}, + "Emacron": {GlyphName: "Emacron", Wx: 667.000000, Wy: 0.000000}, + "Eogonek": {GlyphName: "Eogonek", Wx: 667.000000, Wy: 0.000000}, + "Eth": {GlyphName: "Eth", Wx: 722.000000, Wy: 0.000000}, + "Euro": {GlyphName: "Euro", Wx: 556.000000, Wy: 0.000000}, + "F": {GlyphName: "F", Wx: 611.000000, Wy: 0.000000}, + "G": {GlyphName: "G", Wx: 778.000000, Wy: 0.000000}, + "Gbreve": {GlyphName: "Gbreve", Wx: 778.000000, Wy: 0.000000}, + "Gcommaaccent": {GlyphName: "Gcommaaccent", Wx: 778.000000, Wy: 0.000000}, + "H": {GlyphName: "H", Wx: 722.000000, Wy: 0.000000}, + "I": {GlyphName: "I", Wx: 278.000000, Wy: 0.000000}, + "Iacute": {GlyphName: "Iacute", Wx: 278.000000, Wy: 0.000000}, + "Icircumflex": {GlyphName: "Icircumflex", Wx: 278.000000, Wy: 0.000000}, + "Idieresis": {GlyphName: "Idieresis", Wx: 278.000000, Wy: 0.000000}, + "Idotaccent": {GlyphName: "Idotaccent", Wx: 278.000000, Wy: 0.000000}, + "Igrave": {GlyphName: "Igrave", Wx: 278.000000, Wy: 0.000000}, + "Imacron": {GlyphName: "Imacron", Wx: 278.000000, Wy: 0.000000}, + "Iogonek": {GlyphName: "Iogonek", Wx: 278.000000, Wy: 0.000000}, + "J": {GlyphName: "J", Wx: 500.000000, Wy: 0.000000}, + "K": {GlyphName: "K", Wx: 667.000000, Wy: 0.000000}, + "Kcommaaccent": {GlyphName: "Kcommaaccent", Wx: 667.000000, Wy: 0.000000}, + "L": {GlyphName: "L", Wx: 556.000000, Wy: 0.000000}, + "Lacute": {GlyphName: "Lacute", Wx: 556.000000, Wy: 0.000000}, + "Lcaron": {GlyphName: "Lcaron", Wx: 556.000000, Wy: 0.000000}, + "Lcommaaccent": {GlyphName: "Lcommaaccent", Wx: 556.000000, Wy: 0.000000}, + "Lslash": {GlyphName: "Lslash", Wx: 556.000000, Wy: 0.000000}, + "M": {GlyphName: "M", Wx: 833.000000, Wy: 0.000000}, + "N": {GlyphName: "N", Wx: 722.000000, Wy: 0.000000}, + "Nacute": {GlyphName: "Nacute", Wx: 722.000000, Wy: 0.000000}, + "Ncaron": {GlyphName: "Ncaron", Wx: 722.000000, Wy: 0.000000}, + "Ncommaaccent": {GlyphName: "Ncommaaccent", Wx: 722.000000, Wy: 0.000000}, + "Ntilde": {GlyphName: "Ntilde", Wx: 722.000000, Wy: 0.000000}, + "O": {GlyphName: "O", Wx: 778.000000, Wy: 0.000000}, + "OE": {GlyphName: "OE", Wx: 1000.000000, Wy: 0.000000}, + "Oacute": {GlyphName: "Oacute", Wx: 778.000000, Wy: 0.000000}, + "Ocircumflex": {GlyphName: "Ocircumflex", Wx: 778.000000, Wy: 0.000000}, + "Odieresis": {GlyphName: "Odieresis", Wx: 778.000000, Wy: 0.000000}, + "Ograve": {GlyphName: "Ograve", Wx: 778.000000, Wy: 0.000000}, + "Ohungarumlaut": {GlyphName: "Ohungarumlaut", Wx: 778.000000, Wy: 0.000000}, + "Omacron": {GlyphName: "Omacron", Wx: 778.000000, Wy: 0.000000}, + "Oslash": {GlyphName: "Oslash", Wx: 778.000000, Wy: 0.000000}, + "Otilde": {GlyphName: "Otilde", Wx: 778.000000, Wy: 0.000000}, + "P": {GlyphName: "P", Wx: 667.000000, Wy: 0.000000}, + "Q": {GlyphName: "Q", Wx: 778.000000, Wy: 0.000000}, + "R": {GlyphName: "R", Wx: 722.000000, Wy: 0.000000}, + "Racute": {GlyphName: "Racute", Wx: 722.000000, Wy: 0.000000}, + "Rcaron": {GlyphName: "Rcaron", Wx: 722.000000, Wy: 0.000000}, + "Rcommaaccent": {GlyphName: "Rcommaaccent", Wx: 722.000000, Wy: 0.000000}, + "S": {GlyphName: "S", Wx: 667.000000, Wy: 0.000000}, + "Sacute": {GlyphName: "Sacute", Wx: 667.000000, Wy: 0.000000}, + "Scaron": {GlyphName: "Scaron", Wx: 667.000000, Wy: 0.000000}, + "Scedilla": {GlyphName: "Scedilla", Wx: 667.000000, Wy: 0.000000}, + "Scommaaccent": {GlyphName: "Scommaaccent", Wx: 667.000000, Wy: 0.000000}, + "T": {GlyphName: "T", Wx: 611.000000, Wy: 0.000000}, + "Tcaron": {GlyphName: "Tcaron", Wx: 611.000000, Wy: 0.000000}, + "Tcommaaccent": {GlyphName: "Tcommaaccent", Wx: 611.000000, Wy: 0.000000}, + "Thorn": {GlyphName: "Thorn", Wx: 667.000000, Wy: 0.000000}, + "U": {GlyphName: "U", Wx: 722.000000, Wy: 0.000000}, + "Uacute": {GlyphName: "Uacute", Wx: 722.000000, Wy: 0.000000}, + "Ucircumflex": {GlyphName: "Ucircumflex", Wx: 722.000000, Wy: 0.000000}, + "Udieresis": {GlyphName: "Udieresis", Wx: 722.000000, Wy: 0.000000}, + "Ugrave": {GlyphName: "Ugrave", Wx: 722.000000, Wy: 0.000000}, + "Uhungarumlaut": {GlyphName: "Uhungarumlaut", Wx: 722.000000, Wy: 0.000000}, + "Umacron": {GlyphName: "Umacron", Wx: 722.000000, Wy: 0.000000}, + "Uogonek": {GlyphName: "Uogonek", Wx: 722.000000, Wy: 0.000000}, + "Uring": {GlyphName: "Uring", Wx: 722.000000, Wy: 0.000000}, + "V": {GlyphName: "V", Wx: 667.000000, Wy: 0.000000}, + "W": {GlyphName: "W", Wx: 944.000000, Wy: 0.000000}, + "X": {GlyphName: "X", Wx: 667.000000, Wy: 0.000000}, + "Y": {GlyphName: "Y", Wx: 667.000000, Wy: 0.000000}, + "Yacute": {GlyphName: "Yacute", Wx: 667.000000, Wy: 0.000000}, + "Ydieresis": {GlyphName: "Ydieresis", Wx: 667.000000, Wy: 0.000000}, + "Z": {GlyphName: "Z", Wx: 611.000000, Wy: 0.000000}, + "Zacute": {GlyphName: "Zacute", Wx: 611.000000, Wy: 0.000000}, + "Zcaron": {GlyphName: "Zcaron", Wx: 611.000000, Wy: 0.000000}, + "Zdotaccent": {GlyphName: "Zdotaccent", Wx: 611.000000, Wy: 0.000000}, + "a": {GlyphName: "a", Wx: 556.000000, Wy: 0.000000}, + "aacute": {GlyphName: "aacute", Wx: 556.000000, Wy: 0.000000}, + "abreve": {GlyphName: "abreve", Wx: 556.000000, Wy: 0.000000}, + "acircumflex": {GlyphName: "acircumflex", Wx: 556.000000, Wy: 0.000000}, + "acute": {GlyphName: "acute", Wx: 333.000000, Wy: 0.000000}, + "adieresis": {GlyphName: "adieresis", Wx: 556.000000, Wy: 0.000000}, + "ae": {GlyphName: "ae", Wx: 889.000000, Wy: 0.000000}, + "agrave": {GlyphName: "agrave", Wx: 556.000000, Wy: 0.000000}, + "amacron": {GlyphName: "amacron", Wx: 556.000000, Wy: 0.000000}, + "ampersand": {GlyphName: "ampersand", Wx: 667.000000, Wy: 0.000000}, + "aogonek": {GlyphName: "aogonek", Wx: 556.000000, Wy: 0.000000}, + "aring": {GlyphName: "aring", Wx: 556.000000, Wy: 0.000000}, + "asciicircum": {GlyphName: "asciicircum", Wx: 469.000000, Wy: 0.000000}, + "asciitilde": {GlyphName: "asciitilde", Wx: 584.000000, Wy: 0.000000}, + "asterisk": {GlyphName: "asterisk", Wx: 389.000000, Wy: 0.000000}, + "at": {GlyphName: "at", Wx: 1015.000000, Wy: 0.000000}, + "atilde": {GlyphName: "atilde", Wx: 556.000000, Wy: 0.000000}, + "b": {GlyphName: "b", Wx: 556.000000, Wy: 0.000000}, + "backslash": {GlyphName: "backslash", Wx: 278.000000, Wy: 0.000000}, + "bar": {GlyphName: "bar", Wx: 260.000000, Wy: 0.000000}, + "braceleft": {GlyphName: "braceleft", Wx: 334.000000, Wy: 0.000000}, + "braceright": {GlyphName: "braceright", Wx: 334.000000, Wy: 0.000000}, + "bracketleft": {GlyphName: "bracketleft", Wx: 278.000000, Wy: 0.000000}, + "bracketright": {GlyphName: "bracketright", Wx: 278.000000, Wy: 0.000000}, + "breve": {GlyphName: "breve", Wx: 333.000000, Wy: 0.000000}, + "brokenbar": {GlyphName: "brokenbar", Wx: 260.000000, Wy: 0.000000}, + "bullet": {GlyphName: "bullet", Wx: 350.000000, Wy: 0.000000}, + "c": {GlyphName: "c", Wx: 500.000000, Wy: 0.000000}, + "cacute": {GlyphName: "cacute", Wx: 500.000000, Wy: 0.000000}, + "caron": {GlyphName: "caron", Wx: 333.000000, Wy: 0.000000}, + "ccaron": {GlyphName: "ccaron", Wx: 500.000000, Wy: 0.000000}, + "ccedilla": {GlyphName: "ccedilla", Wx: 500.000000, Wy: 0.000000}, + "cedilla": {GlyphName: "cedilla", Wx: 333.000000, Wy: 0.000000}, + "cent": {GlyphName: "cent", Wx: 556.000000, Wy: 0.000000}, + "circumflex": {GlyphName: "circumflex", Wx: 333.000000, Wy: 0.000000}, + "colon": {GlyphName: "colon", Wx: 278.000000, Wy: 0.000000}, + "comma": {GlyphName: "comma", Wx: 278.000000, Wy: 0.000000}, + "commaaccent": {GlyphName: "commaaccent", Wx: 250.000000, Wy: 0.000000}, + "copyright": {GlyphName: "copyright", Wx: 737.000000, Wy: 0.000000}, + "currency": {GlyphName: "currency", Wx: 556.000000, Wy: 0.000000}, + "d": {GlyphName: "d", Wx: 556.000000, Wy: 0.000000}, + "dagger": {GlyphName: "dagger", Wx: 556.000000, Wy: 0.000000}, + "daggerdbl": {GlyphName: "daggerdbl", Wx: 556.000000, Wy: 0.000000}, + "dcaron": {GlyphName: "dcaron", Wx: 643.000000, Wy: 0.000000}, + "dcroat": {GlyphName: "dcroat", Wx: 556.000000, Wy: 0.000000}, + "degree": {GlyphName: "degree", Wx: 400.000000, Wy: 0.000000}, + "dieresis": {GlyphName: "dieresis", Wx: 333.000000, Wy: 0.000000}, + "divide": {GlyphName: "divide", Wx: 584.000000, Wy: 0.000000}, + "dollar": {GlyphName: "dollar", Wx: 556.000000, Wy: 0.000000}, + "dotaccent": {GlyphName: "dotaccent", Wx: 333.000000, Wy: 0.000000}, + "dotlessi": {GlyphName: "dotlessi", Wx: 278.000000, Wy: 0.000000}, + "e": {GlyphName: "e", Wx: 556.000000, Wy: 0.000000}, + "eacute": {GlyphName: "eacute", Wx: 556.000000, Wy: 0.000000}, + "ecaron": {GlyphName: "ecaron", Wx: 556.000000, Wy: 0.000000}, + "ecircumflex": {GlyphName: "ecircumflex", Wx: 556.000000, Wy: 0.000000}, + "edieresis": {GlyphName: "edieresis", Wx: 556.000000, Wy: 0.000000}, + "edotaccent": {GlyphName: "edotaccent", Wx: 556.000000, Wy: 0.000000}, + "egrave": {GlyphName: "egrave", Wx: 556.000000, Wy: 0.000000}, + "eight": {GlyphName: "eight", Wx: 556.000000, Wy: 0.000000}, + "ellipsis": {GlyphName: "ellipsis", Wx: 1000.000000, Wy: 0.000000}, + "emacron": {GlyphName: "emacron", Wx: 556.000000, Wy: 0.000000}, + "emdash": {GlyphName: "emdash", Wx: 1000.000000, Wy: 0.000000}, + "endash": {GlyphName: "endash", Wx: 556.000000, Wy: 0.000000}, + "eogonek": {GlyphName: "eogonek", Wx: 556.000000, Wy: 0.000000}, + "equal": {GlyphName: "equal", Wx: 584.000000, Wy: 0.000000}, + "eth": {GlyphName: "eth", Wx: 556.000000, Wy: 0.000000}, + "exclam": {GlyphName: "exclam", Wx: 278.000000, Wy: 0.000000}, + "exclamdown": {GlyphName: "exclamdown", Wx: 333.000000, Wy: 0.000000}, + "f": {GlyphName: "f", Wx: 278.000000, Wy: 0.000000}, + "fi": {GlyphName: "fi", Wx: 500.000000, Wy: 0.000000}, + "five": {GlyphName: "five", Wx: 556.000000, Wy: 0.000000}, + "fl": {GlyphName: "fl", Wx: 500.000000, Wy: 0.000000}, + "florin": {GlyphName: "florin", Wx: 556.000000, Wy: 0.000000}, + "four": {GlyphName: "four", Wx: 556.000000, Wy: 0.000000}, + "fraction": {GlyphName: "fraction", Wx: 167.000000, Wy: 0.000000}, + "g": {GlyphName: "g", Wx: 556.000000, Wy: 0.000000}, + "gbreve": {GlyphName: "gbreve", Wx: 556.000000, Wy: 0.000000}, + "gcommaaccent": {GlyphName: "gcommaaccent", Wx: 556.000000, Wy: 0.000000}, + "germandbls": {GlyphName: "germandbls", Wx: 611.000000, Wy: 0.000000}, + "grave": {GlyphName: "grave", Wx: 333.000000, Wy: 0.000000}, + "greater": {GlyphName: "greater", Wx: 584.000000, Wy: 0.000000}, + "greaterequal": {GlyphName: "greaterequal", Wx: 549.000000, Wy: 0.000000}, + "guillemotleft": {GlyphName: "guillemotleft", Wx: 556.000000, Wy: 0.000000}, + "guillemotright": {GlyphName: "guillemotright", Wx: 556.000000, Wy: 0.000000}, + "guilsinglleft": {GlyphName: "guilsinglleft", Wx: 333.000000, Wy: 0.000000}, + "guilsinglright": {GlyphName: "guilsinglright", Wx: 333.000000, Wy: 0.000000}, + "h": {GlyphName: "h", Wx: 556.000000, Wy: 0.000000}, + "hungarumlaut": {GlyphName: "hungarumlaut", Wx: 333.000000, Wy: 0.000000}, + "hyphen": {GlyphName: "hyphen", Wx: 333.000000, Wy: 0.000000}, + "i": {GlyphName: "i", Wx: 222.000000, Wy: 0.000000}, + "iacute": {GlyphName: "iacute", Wx: 278.000000, Wy: 0.000000}, + "icircumflex": {GlyphName: "icircumflex", Wx: 278.000000, Wy: 0.000000}, + "idieresis": {GlyphName: "idieresis", Wx: 278.000000, Wy: 0.000000}, + "igrave": {GlyphName: "igrave", Wx: 278.000000, Wy: 0.000000}, + "imacron": {GlyphName: "imacron", Wx: 278.000000, Wy: 0.000000}, + "iogonek": {GlyphName: "iogonek", Wx: 222.000000, Wy: 0.000000}, + "j": {GlyphName: "j", Wx: 222.000000, Wy: 0.000000}, + "k": {GlyphName: "k", Wx: 500.000000, Wy: 0.000000}, + "kcommaaccent": {GlyphName: "kcommaaccent", Wx: 500.000000, Wy: 0.000000}, + "l": {GlyphName: "l", Wx: 222.000000, Wy: 0.000000}, + "lacute": {GlyphName: "lacute", Wx: 222.000000, Wy: 0.000000}, + "lcaron": {GlyphName: "lcaron", Wx: 299.000000, Wy: 0.000000}, + "lcommaaccent": {GlyphName: "lcommaaccent", Wx: 222.000000, Wy: 0.000000}, + "less": {GlyphName: "less", Wx: 584.000000, Wy: 0.000000}, + "lessequal": {GlyphName: "lessequal", Wx: 549.000000, Wy: 0.000000}, + "logicalnot": {GlyphName: "logicalnot", Wx: 584.000000, Wy: 0.000000}, + "lozenge": {GlyphName: "lozenge", Wx: 471.000000, Wy: 0.000000}, + "lslash": {GlyphName: "lslash", Wx: 222.000000, Wy: 0.000000}, + "m": {GlyphName: "m", Wx: 833.000000, Wy: 0.000000}, + "macron": {GlyphName: "macron", Wx: 333.000000, Wy: 0.000000}, + "minus": {GlyphName: "minus", Wx: 584.000000, Wy: 0.000000}, + "mu": {GlyphName: "mu", Wx: 556.000000, Wy: 0.000000}, + "multiply": {GlyphName: "multiply", Wx: 584.000000, Wy: 0.000000}, + "n": {GlyphName: "n", Wx: 556.000000, Wy: 0.000000}, + "nacute": {GlyphName: "nacute", Wx: 556.000000, Wy: 0.000000}, + "ncaron": {GlyphName: "ncaron", Wx: 556.000000, Wy: 0.000000}, + "ncommaaccent": {GlyphName: "ncommaaccent", Wx: 556.000000, Wy: 0.000000}, + "nine": {GlyphName: "nine", Wx: 556.000000, Wy: 0.000000}, + "notequal": {GlyphName: "notequal", Wx: 549.000000, Wy: 0.000000}, + "ntilde": {GlyphName: "ntilde", Wx: 556.000000, Wy: 0.000000}, + "numbersign": {GlyphName: "numbersign", Wx: 556.000000, Wy: 0.000000}, + "o": {GlyphName: "o", Wx: 556.000000, Wy: 0.000000}, + "oacute": {GlyphName: "oacute", Wx: 556.000000, Wy: 0.000000}, + "ocircumflex": {GlyphName: "ocircumflex", Wx: 556.000000, Wy: 0.000000}, + "odieresis": {GlyphName: "odieresis", Wx: 556.000000, Wy: 0.000000}, + "oe": {GlyphName: "oe", Wx: 944.000000, Wy: 0.000000}, + "ogonek": {GlyphName: "ogonek", Wx: 333.000000, Wy: 0.000000}, + "ograve": {GlyphName: "ograve", Wx: 556.000000, Wy: 0.000000}, + "ohungarumlaut": {GlyphName: "ohungarumlaut", Wx: 556.000000, Wy: 0.000000}, + "omacron": {GlyphName: "omacron", Wx: 556.000000, Wy: 0.000000}, + "one": {GlyphName: "one", Wx: 556.000000, Wy: 0.000000}, + "onehalf": {GlyphName: "onehalf", Wx: 834.000000, Wy: 0.000000}, + "onequarter": {GlyphName: "onequarter", Wx: 834.000000, Wy: 0.000000}, + "onesuperior": {GlyphName: "onesuperior", Wx: 333.000000, Wy: 0.000000}, + "ordfeminine": {GlyphName: "ordfeminine", Wx: 370.000000, Wy: 0.000000}, + "ordmasculine": {GlyphName: "ordmasculine", Wx: 365.000000, Wy: 0.000000}, + "oslash": {GlyphName: "oslash", Wx: 611.000000, Wy: 0.000000}, + "otilde": {GlyphName: "otilde", Wx: 556.000000, Wy: 0.000000}, + "p": {GlyphName: "p", Wx: 556.000000, Wy: 0.000000}, + "paragraph": {GlyphName: "paragraph", Wx: 537.000000, Wy: 0.000000}, + "parenleft": {GlyphName: "parenleft", Wx: 333.000000, Wy: 0.000000}, + "parenright": {GlyphName: "parenright", Wx: 333.000000, Wy: 0.000000}, + "partialdiff": {GlyphName: "partialdiff", Wx: 476.000000, Wy: 0.000000}, + "percent": {GlyphName: "percent", Wx: 889.000000, Wy: 0.000000}, + "period": {GlyphName: "period", Wx: 278.000000, Wy: 0.000000}, + "periodcentered": {GlyphName: "periodcentered", Wx: 278.000000, Wy: 0.000000}, + "perthousand": {GlyphName: "perthousand", Wx: 1000.000000, Wy: 0.000000}, + "plus": {GlyphName: "plus", Wx: 584.000000, Wy: 0.000000}, + "plusminus": {GlyphName: "plusminus", Wx: 584.000000, Wy: 0.000000}, + "q": {GlyphName: "q", Wx: 556.000000, Wy: 0.000000}, + "question": {GlyphName: "question", Wx: 556.000000, Wy: 0.000000}, + "questiondown": {GlyphName: "questiondown", Wx: 611.000000, Wy: 0.000000}, + "quotedbl": {GlyphName: "quotedbl", Wx: 355.000000, Wy: 0.000000}, + "quotedblbase": {GlyphName: "quotedblbase", Wx: 333.000000, Wy: 0.000000}, + "quotedblleft": {GlyphName: "quotedblleft", Wx: 333.000000, Wy: 0.000000}, + "quotedblright": {GlyphName: "quotedblright", Wx: 333.000000, Wy: 0.000000}, + "quoteleft": {GlyphName: "quoteleft", Wx: 222.000000, Wy: 0.000000}, + "quoteright": {GlyphName: "quoteright", Wx: 222.000000, Wy: 0.000000}, + "quotesinglbase": {GlyphName: "quotesinglbase", Wx: 222.000000, Wy: 0.000000}, + "quotesingle": {GlyphName: "quotesingle", Wx: 191.000000, Wy: 0.000000}, + "r": {GlyphName: "r", Wx: 333.000000, Wy: 0.000000}, + "racute": {GlyphName: "racute", Wx: 333.000000, Wy: 0.000000}, + "radical": {GlyphName: "radical", Wx: 453.000000, Wy: 0.000000}, + "rcaron": {GlyphName: "rcaron", Wx: 333.000000, Wy: 0.000000}, + "rcommaaccent": {GlyphName: "rcommaaccent", Wx: 333.000000, Wy: 0.000000}, + "registered": {GlyphName: "registered", Wx: 737.000000, Wy: 0.000000}, + "ring": {GlyphName: "ring", Wx: 333.000000, Wy: 0.000000}, + "s": {GlyphName: "s", Wx: 500.000000, Wy: 0.000000}, + "sacute": {GlyphName: "sacute", Wx: 500.000000, Wy: 0.000000}, + "scaron": {GlyphName: "scaron", Wx: 500.000000, Wy: 0.000000}, + "scedilla": {GlyphName: "scedilla", Wx: 500.000000, Wy: 0.000000}, + "scommaaccent": {GlyphName: "scommaaccent", Wx: 500.000000, Wy: 0.000000}, + "section": {GlyphName: "section", Wx: 556.000000, Wy: 0.000000}, + "semicolon": {GlyphName: "semicolon", Wx: 278.000000, Wy: 0.000000}, + "seven": {GlyphName: "seven", Wx: 556.000000, Wy: 0.000000}, + "six": {GlyphName: "six", Wx: 556.000000, Wy: 0.000000}, + "slash": {GlyphName: "slash", Wx: 278.000000, Wy: 0.000000}, + "space": {GlyphName: "space", Wx: 278.000000, Wy: 0.000000}, + "sterling": {GlyphName: "sterling", Wx: 556.000000, Wy: 0.000000}, + "summation": {GlyphName: "summation", Wx: 600.000000, Wy: 0.000000}, + "t": {GlyphName: "t", Wx: 278.000000, Wy: 0.000000}, + "tcaron": {GlyphName: "tcaron", Wx: 317.000000, Wy: 0.000000}, + "tcommaaccent": {GlyphName: "tcommaaccent", Wx: 278.000000, Wy: 0.000000}, + "thorn": {GlyphName: "thorn", Wx: 556.000000, Wy: 0.000000}, + "three": {GlyphName: "three", Wx: 556.000000, Wy: 0.000000}, + "threequarters": {GlyphName: "threequarters", Wx: 834.000000, Wy: 0.000000}, + "threesuperior": {GlyphName: "threesuperior", Wx: 333.000000, Wy: 0.000000}, + "tilde": {GlyphName: "tilde", Wx: 333.000000, Wy: 0.000000}, + "trademark": {GlyphName: "trademark", Wx: 1000.000000, Wy: 0.000000}, + "two": {GlyphName: "two", Wx: 556.000000, Wy: 0.000000}, + "twosuperior": {GlyphName: "twosuperior", Wx: 333.000000, Wy: 0.000000}, + "u": {GlyphName: "u", Wx: 556.000000, Wy: 0.000000}, + "uacute": {GlyphName: "uacute", Wx: 556.000000, Wy: 0.000000}, + "ucircumflex": {GlyphName: "ucircumflex", Wx: 556.000000, Wy: 0.000000}, + "udieresis": {GlyphName: "udieresis", Wx: 556.000000, Wy: 0.000000}, + "ugrave": {GlyphName: "ugrave", Wx: 556.000000, Wy: 0.000000}, + "uhungarumlaut": {GlyphName: "uhungarumlaut", Wx: 556.000000, Wy: 0.000000}, + "umacron": {GlyphName: "umacron", Wx: 556.000000, Wy: 0.000000}, + "underscore": {GlyphName: "underscore", Wx: 556.000000, Wy: 0.000000}, + "uogonek": {GlyphName: "uogonek", Wx: 556.000000, Wy: 0.000000}, + "uring": {GlyphName: "uring", Wx: 556.000000, Wy: 0.000000}, + "v": {GlyphName: "v", Wx: 500.000000, Wy: 0.000000}, + "w": {GlyphName: "w", Wx: 722.000000, Wy: 0.000000}, + "x": {GlyphName: "x", Wx: 500.000000, Wy: 0.000000}, + "y": {GlyphName: "y", Wx: 500.000000, Wy: 0.000000}, + "yacute": {GlyphName: "yacute", Wx: 500.000000, Wy: 0.000000}, + "ydieresis": {GlyphName: "ydieresis", Wx: 500.000000, Wy: 0.000000}, + "yen": {GlyphName: "yen", Wx: 556.000000, Wy: 0.000000}, + "z": {GlyphName: "z", Wx: 500.000000, Wy: 0.000000}, + "zacute": {GlyphName: "zacute", Wx: 500.000000, Wy: 0.000000}, + "zcaron": {GlyphName: "zcaron", Wx: 500.000000, Wy: 0.000000}, + "zdotaccent": {GlyphName: "zdotaccent", Wx: 500.000000, Wy: 0.000000}, + "zero": {GlyphName: "zero", Wx: 556.000000, Wy: 0.000000}, } diff --git a/pdf/model/image.go b/pdf/model/image.go index 03fe72aa..48e09ba2 100644 --- a/pdf/model/image.go +++ b/pdf/model/image.go @@ -124,7 +124,7 @@ func (this *Image) ToGoImage() (goimage.Image, error) { b := uint16(samples[i+4])<<8 | uint16(samples[i+5]) a := uint16(0) if this.alphaData != nil && len(this.alphaData) > aidx+1 { - a = uint16(this.alphaData[aidx]<<8) | uint16(this.alphaData[aidx+1]) + a = (uint16(this.alphaData[aidx]) << 8) | uint16(this.alphaData[aidx+1]) aidx += 2 } c = gocolor.RGBA64{R: r, G: g, B: b, A: a} diff --git a/pdf/model/outlines.go b/pdf/model/outlines.go index 6d97b821..2d394950 100644 --- a/pdf/model/outlines.go +++ b/pdf/model/outlines.go @@ -100,7 +100,7 @@ func newPdfOutlineFromIndirectObject(container *PdfIndirectObject) (*PdfOutline, typeVal, ok := obj.(*PdfObjectName) if ok { if *typeVal != "Outlines" { - common.Log.Error("Type != Outlines (%s)", *typeVal) + common.Log.Debug("ERROR Type != Outlines (%s)", *typeVal) // Should be "Outlines" if there, but some files have other types // Log as an error but do not quit. // Might be a good idea to log this kind of deviation from the standard separately. @@ -213,7 +213,7 @@ func (n *PdfOutlineTreeNode) getOuter() PdfModel { return outlineItem } - common.Log.Error("Invalid outline tree node item") // Should never happen. + common.Log.Debug("ERROR Invalid outline tree node item") // Should never happen. return nil } diff --git a/pdf/model/page.go b/pdf/model/page.go index b6ac7599..9f160854 100644 --- a/pdf/model/page.go +++ b/pdf/model/page.go @@ -400,10 +400,6 @@ func (this *PdfPage) GetMediaBox() (*PdfRectangle, error) { return nil, errors.New("Media box not defined") } -// GetResources returns the inheritable resources, either from the page or or a higher up page/pages struct. -func (this *PdfPage) GetResources() (*PdfPageResources, error) { - return this.getResources() -} // Get the inheritable resources, either from the page or or a higher up page/pages struct. func (this *PdfPage) getResources() (*PdfPageResources, error) { @@ -513,7 +509,7 @@ func (this *PdfPage) GetPageDict() *PdfObjectDictionary { return p } -// Get the page object as an indirect object. Wraps the Page +// Get the page object as an indirect objects. Wraps the Page // dictionary into an indirect object. func (this *PdfPage) GetPageAsIndirectObject() *PdfIndirectObject { return this.primitive diff --git a/pdf/model/reader.go b/pdf/model/reader.go index a22d3268..4fae5f73 100644 --- a/pdf/model/reader.go +++ b/pdf/model/reader.go @@ -32,8 +32,6 @@ type PdfReader struct { traversed map[PdfObject]bool } -// NewPdfReader returns a PdfReader -// the trailer, cross-reference table and "structure" are loaded. func NewPdfReader(rs io.ReadSeeker) (*PdfReader, error) { pdfReader := &PdfReader{} pdfReader.traversed = map[PdfObject]bool{} @@ -149,7 +147,7 @@ func (this *PdfReader) loadStructure() error { } oc, err := this.parser.LookupByReference(*root) if err != nil { - common.Log.Error("Failed to read root element catalog: %s", err) + common.Log.Debug("ERROR: Failed to read root element catalog: %s", err) return err } pcatalog, ok := oc.(*PdfIndirectObject) @@ -159,7 +157,7 @@ func (this *PdfReader) loadStructure() error { } catalog, ok := (*pcatalog).PdfObject.(*PdfObjectDictionary) if !ok { - common.Log.Error("Invalid catalog (%s)", pcatalog.PdfObject) + common.Log.Debug("ERROR: Invalid catalog (%s)", pcatalog.PdfObject) return errors.New("Invalid catalog") } common.Log.Trace("Catalog: %s", catalog) @@ -171,23 +169,23 @@ func (this *PdfReader) loadStructure() error { } op, err := this.parser.LookupByReference(*pagesRef) if err != nil { - common.Log.Error("Failed to read pages") + common.Log.Debug("ERROR: Failed to read pages") return err } ppages, ok := op.(*PdfIndirectObject) if !ok { - common.Log.Error("Pages object invalid") - common.Log.Error("op: %p", ppages) + common.Log.Debug("ERROR: Pages object invalid") + common.Log.Debug("op: %p", ppages) return errors.New("Pages object invalid") } pages, ok := ppages.PdfObject.(*PdfObjectDictionary) if !ok { - common.Log.Error("Pages object invalid (%s)", ppages) + common.Log.Debug("ERROR: Pages object invalid (%s)", ppages) return errors.New("Pages object invalid") } pageCount, ok := pages.Get("Count").(*PdfObjectInteger) if !ok { - common.Log.Error("Pages count object invalid") + common.Log.Debug("ERROR: Pages count object invalid") return errors.New("Pages count invalid") } @@ -210,7 +208,7 @@ func (this *PdfReader) loadStructure() error { // Outlines. this.outlineTree, err = this.loadOutlines() if err != nil { - common.Log.Error("Failed to build outline tree (%s)", err) + common.Log.Debug("ERROR: Failed to build outline tree (%s)", err) return err } @@ -272,7 +270,7 @@ func (this *PdfReader) loadOutlines() (*PdfOutlineTreeNode, error) { // Trace references to the object. outlineRootObj, err := this.traceToObject(outlinesObj) if err != nil { - common.Log.Error("Failed to read outlines") + common.Log.Debug("ERROR: Failed to read outlines") return nil, err } common.Log.Trace("Outline root: %v", outlineRootObj) @@ -424,7 +422,7 @@ func (this *PdfReader) GetOutlinesFlattened() ([]*PdfOutlineTreeNode, []string, return } if node.context == nil { - common.Log.Error("Missing node.context") // Should not happen ever. + common.Log.Debug("ERROR: Missing node.context") // Should not happen ever. return } @@ -483,7 +481,7 @@ func (this *PdfReader) loadForms() (*PdfAcroForm, error) { common.Log.Trace("Traverse the Acroforms structure") err = this.traverseObjectData(formsDict) if err != nil { - common.Log.Error("Unable to traverse AcroForms (%s)", err) + common.Log.Debug("ERROR: Unable to traverse AcroForms (%s)", err) return nil, err } @@ -543,7 +541,7 @@ func (this *PdfReader) buildPageList(node *PdfIndirectObject, parent *PdfIndirec return nil } if *objType != "Pages" { - common.Log.Error("Table of content containing non Page/Pages object! (%s)", objType) + common.Log.Debug("ERROR: Table of content containing non Page/Pages object! (%s)", objType) return errors.New("Table of content containing non Page/Pages object!") } @@ -560,7 +558,7 @@ func (this *PdfReader) buildPageList(node *PdfIndirectObject, parent *PdfIndirec kidsObj, err := this.parser.Trace(nodeDict.Get("Kids")) if err != nil { - common.Log.Error("Failed loading Kids object") + common.Log.Debug("ERROR: Failed loading Kids object") return err } @@ -580,7 +578,7 @@ func (this *PdfReader) buildPageList(node *PdfIndirectObject, parent *PdfIndirec for idx, child := range *kids { child, ok := child.(*PdfIndirectObject) if !ok { - common.Log.Error("Page not indirect object - (%s)", child) + common.Log.Debug("ERROR: Page not indirect object - (%s)", child) return errors.New("Page not indirect object") } (*kids)[idx] = child @@ -692,7 +690,7 @@ func (this *PdfReader) traverseObjectData(o PdfObject) error { } if _, isRef := o.(*PdfObjectReference); isRef { - common.Log.Error("Reader tracing a reference!") + common.Log.Debug("ERROR: Reader tracing a reference!") return errors.New("Reader tracing a reference!") } @@ -763,6 +761,15 @@ func (this *PdfReader) Inspect() (map[string]int, error) { return this.parser.Inspect() } +// GetObjectNums returns the object numbers of the PDF objects in the file +// Numbered objects are either indirect objects or stream objects. +// e.g. objNums := pdfReader.GetObjectNums() +// The underlying objects can then be accessed with +// pdfReader.GetIndirectObjectByNumber(objNums[0]) for the first available object. +func (r *PdfReader) GetObjectNums() []int { + return r.parser.GetObjectNums() +} + // Get specific object number. func (this *PdfReader) GetIndirectObjectByNumber(number int) (PdfObject, error) { obj, err := this.parser.LookupByNumber(number) diff --git a/pdf/model/resources.go b/pdf/model/resources.go index 80f4cadf..95c84762 100644 --- a/pdf/model/resources.go +++ b/pdf/model/resources.go @@ -138,7 +138,7 @@ func (r *PdfPageResources) GetShadingByName(keyName PdfObjectName) (*PdfShading, return nil, false } - shadingDict, ok := r.Shading.(*PdfObjectDictionary) + shadingDict, ok := TraceToDirectObject(r.Shading).(*PdfObjectDictionary) if !ok { common.Log.Debug("ERROR: Invalid Shading entry - not a dict (got %T)", r.Shading) return nil, false @@ -178,7 +178,7 @@ func (r *PdfPageResources) GetPatternByName(keyName PdfObjectName) (*PdfPattern, return nil, false } - patternDict, ok := r.Pattern.(*PdfObjectDictionary) + patternDict, ok := TraceToDirectObject(r.Pattern).(*PdfObjectDictionary) if !ok { common.Log.Debug("ERROR: Invalid Pattern entry - not a dict (got %T)", r.Pattern) return nil, false @@ -326,7 +326,7 @@ func (r *PdfPageResources) GetXObjectByName(keyName PdfObjectName) (*PdfObjectSt } dict := stream.PdfObjectDictionary - name, ok := dict.Get("Subtype").(*PdfObjectName) + name, ok := TraceToDirectObject(dict.Get("Subtype")).(*PdfObjectName) if !ok { common.Log.Debug("XObject Subtype not a Name, dict: %s", dict.String()) return nil, XObjectTypeUndefined diff --git a/pdf/model/textencoding/glyphlist/encoding-list.go b/pdf/model/textencoding/glyphlist/utils/encoding-list/encoding-list.go similarity index 99% rename from pdf/model/textencoding/glyphlist/encoding-list.go rename to pdf/model/textencoding/glyphlist/utils/encoding-list/encoding-list.go index c79add0b..9c222dee 100644 --- a/pdf/model/textencoding/glyphlist/encoding-list.go +++ b/pdf/model/textencoding/glyphlist/utils/encoding-list/encoding-list.go @@ -1,3 +1,5 @@ +// +build unidev + package main // Utility to generate static maps of glyph <-> character codes for text encoding. diff --git a/pdf/model/textencoding/glyphlist/glyphparser.go b/pdf/model/textencoding/glyphlist/utils/glyphparser/glyphparser.go similarity index 97% rename from pdf/model/textencoding/glyphlist/glyphparser.go rename to pdf/model/textencoding/glyphlist/utils/glyphparser/glyphparser.go index abd711c9..c28e21d7 100644 --- a/pdf/model/textencoding/glyphlist/glyphparser.go +++ b/pdf/model/textencoding/glyphlist/utils/glyphparser/glyphparser.go @@ -1,3 +1,5 @@ +// +build unidev + package main // Utility to generate static maps of glyph <-> rune conversions for a glyphlist. @@ -56,8 +58,8 @@ func main() { func printGlyphToRuneList(glyphToUnicodeMap map[string]string) { keys := []string{} - for k, _ := range glyphToUnicodeMap { - keys = append(keys, k) + for key := range glyphToUnicodeMap { + keys = append(keys, key) } sort.Strings(keys) @@ -71,8 +73,8 @@ func printGlyphToRuneList(glyphToUnicodeMap map[string]string) { func printRuneToGlyphList(glyphToUnicodeMap map[string]string) { keys := []string{} - for k, _ := range glyphToUnicodeMap { - keys = append(keys, k) + for key := range glyphToUnicodeMap { + keys = append(keys, key) } sort.Strings(keys) diff --git a/pdf/model/writer.go b/pdf/model/writer.go index 7a2fcc5c..65089a73 100644 --- a/pdf/model/writer.go +++ b/pdf/model/writer.go @@ -157,8 +157,9 @@ func (this *PdfWriter) hasObject(obj PdfObject) bool { return false } -// addObject adds `obj` to list of objects. -// Returns true if `obj` was not already in list of objects, or false it was. +// Adds the object to list of objects and returns true if the obj was +// not already added. +// Returns false if the object was previously added. func (this *PdfWriter) addObject(obj PdfObject) bool { hasObj := this.hasObject(obj) if !hasObj { @@ -169,9 +170,6 @@ func (this *PdfWriter) addObject(obj PdfObject) bool { return false } -// addObjects recursively adds `obj` to the list of objects. -// If `obj` is a container then its elements are added with a recursive call to addObjects. -// In `obj` is a PdfIndirectObject or PdfObjectStream, its contents are added after it is added. func (this *PdfWriter) addObjects(obj PdfObject) error { common.Log.Trace("Adding objects!") @@ -228,7 +226,7 @@ func (this *PdfWriter) addObjects(obj PdfObject) error { // Could refer to somewhere outside of the scope of the output doc. // Should be done by the reader already. // -> ERROR. - common.Log.Error("Parent is a reference object - Cannot be in writer (needs to be resolved)") + common.Log.Debug("ERROR: Parent is a reference object - Cannot be in writer (needs to be resolved)") return fmt.Errorf("Parent is a reference object - Cannot be in writer (needs to be resolved) - %s", parentObj) } } @@ -253,14 +251,14 @@ func (this *PdfWriter) addObjects(obj PdfObject) error { if _, isReference := obj.(*PdfObjectReference); isReference { // Should never be a reference, should already be resolved. - common.Log.Error("Cannot be a reference!") + common.Log.Debug("ERROR: Cannot be a reference!") return errors.New("Reference not allowed") } return nil } -// AddPage adds a page to the PDF file. The new page should be an indirect +// Add a page to the PDF file. The new page should be an indirect // object. func (this *PdfWriter) AddPage(page *PdfPage) error { obj := page.ToPdfObject() @@ -269,14 +267,14 @@ func (this *PdfWriter) AddPage(page *PdfPage) error { pageObj, ok := obj.(*PdfIndirectObject) if !ok { - return errors.New("Page should be an indirect object.") + return errors.New("Page should be an indirect object") } common.Log.Trace("%s", pageObj) common.Log.Trace("%s", pageObj.PdfObject) pDict, ok := pageObj.PdfObject.(*PdfObjectDictionary) if !ok { - return errors.New("Page object should be a dictionary.") + return errors.New("Page object should be a dictionary") } otype, ok := pDict.Get("Type").(*PdfObjectName) @@ -517,7 +515,7 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio return nil } -// Write out the pdf. +// Write the pdf out. func (this *PdfWriter) Write(ws io.WriteSeeker) error { common.Log.Trace("Write()") // Outlines. diff --git a/pdf/model/xobject.go b/pdf/model/xobject.go index ae41d0ed..43ed9c57 100644 --- a/pdf/model/xobject.go +++ b/pdf/model/xobject.go @@ -306,7 +306,7 @@ func NewXObjectImageFromStream(stream *PdfObjectStream) (*XObjectImage, error) { } img.Filter = encoder - if obj := dict.Get("Width"); obj != nil { + if obj := TraceToDirectObject(dict.Get("Width")); obj != nil { iObj, ok := obj.(*PdfObjectInteger) if !ok { return nil, errors.New("Invalid image width object") @@ -317,7 +317,7 @@ func NewXObjectImageFromStream(stream *PdfObjectStream) (*XObjectImage, error) { return nil, errors.New("Width missing") } - if obj := dict.Get("Height"); obj != nil { + if obj := TraceToDirectObject(dict.Get("Height")); obj != nil { iObj, ok := obj.(*PdfObjectInteger) if !ok { return nil, errors.New("Invalid image height object") @@ -328,7 +328,7 @@ func NewXObjectImageFromStream(stream *PdfObjectStream) (*XObjectImage, error) { return nil, errors.New("Height missing") } - if obj := dict.Get("ColorSpace"); obj != nil { + if obj := TraceToDirectObject(dict.Get("ColorSpace")); obj != nil { cs, err := newPdfColorspaceFromPdfObject(obj) if err != nil { return nil, err @@ -340,7 +340,7 @@ func NewXObjectImageFromStream(stream *PdfObjectStream) (*XObjectImage, error) { img.ColorSpace = NewPdfColorspaceDeviceGray() } - if obj := dict.Get("BitsPerComponent"); obj != nil { + if obj := TraceToDirectObject(dict.Get("BitsPerComponent")); obj != nil { iObj, ok := obj.(*PdfObjectInteger) if !ok { return nil, errors.New("Invalid image height object") diff --git a/pdf/ps/exec.go b/pdf/ps/exec.go index 16ad1c6b..45d1b097 100644 --- a/pdf/ps/exec.go +++ b/pdf/ps/exec.go @@ -9,6 +9,8 @@ package ps import ( "fmt" + + "github.com/unidoc/unidoc/common" ) // A PSExecutor has its own execution stack and is used to executre a PS routine (program). @@ -52,6 +54,7 @@ func (this *PSExecutor) Execute(objects []PSObject) ([]PSObject, error) { err := this.program.Exec(this.Stack) if err != nil { + common.Log.Debug("Exec failed: %v", err) return nil, err } diff --git a/pdf/ps/object.go b/pdf/ps/object.go index fdaadeda..2a52a995 100644 --- a/pdf/ps/object.go +++ b/pdf/ps/object.go @@ -587,6 +587,12 @@ func (this *PSOperand) Dup(stack *PSStack) error { return err } + // Push it back. + err = stack.Push(obj) + if err != nil { + return err + } + // Push the duplicate. err = stack.Push(obj.Duplicate()) return err } @@ -1217,7 +1223,7 @@ func (this *PSOperand) Roll(stack *PSStack) error { } else { // if j < 0: put the bottom element on top bottom := substack[len(substack)-n.Val] - substack = append(substack[1:len(substack)], bottom) + substack = append(substack[1:], bottom) } s := append((*stack)[0:len(*stack)-n.Val], substack...) diff --git a/pdf/ps/parser_test.go b/pdf/ps/parser_test.go index ced3d6a4..d1ea45fa 100644 --- a/pdf/ps/parser_test.go +++ b/pdf/ps/parser_test.go @@ -16,7 +16,8 @@ import ( ) func init() { - common.SetLogger(common.ConsoleLogger{}) + common.SetLogger(common.NewConsoleLogger(common.LogLevelDebug)) + //common.SetLogger(common.NewConsoleLogger(common.LogLevelTrace)) } func makeReaderForText(txt string) *bufio.Reader { @@ -270,6 +271,8 @@ func TestFunctionOperations(t *testing.T) { func TestVariousCases(t *testing.T) { testcases := []ComplexTestEntry{ + // dup + {progText: "{ 99 dup }", expected: "[ int:99 int:99 ]"}, // ceiling {progText: "{ 3.2 ceiling }", expected: "[ real:4.00000 ]"}, {progText: "{ -4.8 ceiling }", expected: "[ real:-4.00000 ]"}, @@ -405,3 +408,24 @@ func TestVariousCases(t *testing.T) { } } } + +func TestTintTransform1(t *testing.T) { + testcases := []ComplexTestEntry{ + // from corpus epson_pages3_color_pages1.pdf. + {progText: "{ 0.0000 dup 0 mul exch dup 0 mul exch dup 0 mul exch 1 mul }", expected: "[ real:0.00000 real:0.00000 real:0.00000 real:0.00000 ]"}, + } + + for _, testcase := range testcases { + stack, err := quickTest(testcase.progText) + if err != nil { + t.Errorf("Error: %v", err) + return + } + + // Maybe not the most robust test (comparing the strings), but should do. + if stack.DebugString() != testcase.expected { + t.Errorf("Wrong result: '%s' != '%s'", stack.DebugString(), testcase.expected) + return + } + } +} diff --git a/testfiles/roboto/COPYRIGHT.txt b/testfiles/roboto/COPYRIGHT.txt new file mode 100644 index 00000000..a7ef6993 --- /dev/null +++ b/testfiles/roboto/COPYRIGHT.txt @@ -0,0 +1 @@ +Copyright 2011 Google Inc. All Rights Reserved. \ No newline at end of file diff --git a/testfiles/roboto/DESCRIPTION.en_us.html b/testfiles/roboto/DESCRIPTION.en_us.html new file mode 100644 index 00000000..e90fedd9 --- /dev/null +++ b/testfiles/roboto/DESCRIPTION.en_us.html @@ -0,0 +1,13 @@ +

+Roboto has a dual nature. +It has a mechanical skeleton and the forms are largely geometric. +At the same time, the font features friendly and open curves. +While some grotesks distort their letterforms to force a rigid rhythm, Roboto doesn’t compromise, allowing letters to be settled into their natural width. +This makes for a more natural reading rhythm more commonly found in humanist and serif types. +

+

+This is the regular family, which can be used alongside the Roboto Condensed family and the Roboto Slab family. +

+

+To contribute, see github.com/google/roboto +

diff --git a/testfiles/roboto/LICENSE.txt b/testfiles/roboto/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/testfiles/roboto/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/testfiles/roboto/METADATA.pb b/testfiles/roboto/METADATA.pb new file mode 100644 index 00000000..a4d2bb22 --- /dev/null +++ b/testfiles/roboto/METADATA.pb @@ -0,0 +1,121 @@ +name: "Roboto" +designer: "Christian Robertson" +license: "APACHE2" +category: "SANS_SERIF" +date_added: "2013-01-09" +fonts { + name: "Roboto" + style: "normal" + weight: 100 + filename: "Roboto-Thin.ttf" + post_script_name: "Roboto-Thin" + full_name: "Roboto Thin" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "italic" + weight: 100 + filename: "Roboto-ThinItalic.ttf" + post_script_name: "Roboto-ThinItalic" + full_name: "Roboto Thin Italic" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "normal" + weight: 300 + filename: "Roboto-Light.ttf" + post_script_name: "Roboto-Light" + full_name: "Roboto Light" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "italic" + weight: 300 + filename: "Roboto-LightItalic.ttf" + post_script_name: "Roboto-LightItalic" + full_name: "Roboto Light Italic" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "normal" + weight: 400 + filename: "Roboto-Regular.ttf" + post_script_name: "Roboto-Regular" + full_name: "Roboto" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "italic" + weight: 400 + filename: "Roboto-Italic.ttf" + post_script_name: "Roboto-Italic" + full_name: "Roboto Italic" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "normal" + weight: 500 + filename: "Roboto-Medium.ttf" + post_script_name: "Roboto-Medium" + full_name: "Roboto Medium" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "italic" + weight: 500 + filename: "Roboto-MediumItalic.ttf" + post_script_name: "Roboto-MediumItalic" + full_name: "Roboto Medium Italic" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "normal" + weight: 700 + filename: "Roboto-Bold.ttf" + post_script_name: "Roboto-Bold" + full_name: "Roboto Bold" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "italic" + weight: 700 + filename: "Roboto-BoldItalic.ttf" + post_script_name: "Roboto-BoldItalic" + full_name: "Roboto Bold Italic" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "normal" + weight: 900 + filename: "Roboto-Black.ttf" + post_script_name: "Roboto-Black" + full_name: "Roboto Black" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +fonts { + name: "Roboto" + style: "italic" + weight: 900 + filename: "Roboto-BlackItalic.ttf" + post_script_name: "Roboto-BlackItalic" + full_name: "Roboto Black Italic" + copyright: "Copyright 2011 Google Inc. All Rights Reserved." +} +subsets: "menu" +subsets: "cyrillic" +subsets: "cyrillic-ext" +subsets: "greek" +subsets: "greek-ext" +subsets: "latin" +subsets: "latin-ext" +subsets: "vietnamese" diff --git a/testfiles/roboto/Roboto-Black.ttf b/testfiles/roboto/Roboto-Black.ttf new file mode 100644 index 00000000..689fe5cb Binary files /dev/null and b/testfiles/roboto/Roboto-Black.ttf differ diff --git a/testfiles/roboto/Roboto-BlackItalic.ttf b/testfiles/roboto/Roboto-BlackItalic.ttf new file mode 100644 index 00000000..0b4e0ee1 Binary files /dev/null and b/testfiles/roboto/Roboto-BlackItalic.ttf differ diff --git a/testfiles/roboto/Roboto-Bold.ttf b/testfiles/roboto/Roboto-Bold.ttf new file mode 100644 index 00000000..d3f01ad2 Binary files /dev/null and b/testfiles/roboto/Roboto-Bold.ttf differ diff --git a/testfiles/roboto/Roboto-BoldItalic.ttf b/testfiles/roboto/Roboto-BoldItalic.ttf new file mode 100644 index 00000000..41cc1e75 Binary files /dev/null and b/testfiles/roboto/Roboto-BoldItalic.ttf differ diff --git a/testfiles/roboto/Roboto-Italic.ttf b/testfiles/roboto/Roboto-Italic.ttf new file mode 100644 index 00000000..6a1cee5b Binary files /dev/null and b/testfiles/roboto/Roboto-Italic.ttf differ diff --git a/testfiles/roboto/Roboto-Light.ttf b/testfiles/roboto/Roboto-Light.ttf new file mode 100644 index 00000000..219063a5 Binary files /dev/null and b/testfiles/roboto/Roboto-Light.ttf differ diff --git a/testfiles/roboto/Roboto-LightItalic.ttf b/testfiles/roboto/Roboto-LightItalic.ttf new file mode 100644 index 00000000..0e81e876 Binary files /dev/null and b/testfiles/roboto/Roboto-LightItalic.ttf differ diff --git a/testfiles/roboto/Roboto-Medium.ttf b/testfiles/roboto/Roboto-Medium.ttf new file mode 100644 index 00000000..1a7f3b0b Binary files /dev/null and b/testfiles/roboto/Roboto-Medium.ttf differ diff --git a/testfiles/roboto/Roboto-MediumItalic.ttf b/testfiles/roboto/Roboto-MediumItalic.ttf new file mode 100644 index 00000000..00302952 Binary files /dev/null and b/testfiles/roboto/Roboto-MediumItalic.ttf differ diff --git a/testfiles/roboto/Roboto-Regular.ttf b/testfiles/roboto/Roboto-Regular.ttf new file mode 100644 index 00000000..2c97eead Binary files /dev/null and b/testfiles/roboto/Roboto-Regular.ttf differ diff --git a/testfiles/roboto/Roboto-Thin.ttf b/testfiles/roboto/Roboto-Thin.ttf new file mode 100644 index 00000000..b74a4fd1 Binary files /dev/null and b/testfiles/roboto/Roboto-Thin.ttf differ diff --git a/testfiles/roboto/Roboto-ThinItalic.ttf b/testfiles/roboto/Roboto-ThinItalic.ttf new file mode 100644 index 00000000..dd0ddb85 Binary files /dev/null and b/testfiles/roboto/Roboto-ThinItalic.ttf differ diff --git a/wercker.yml b/wercker.yml index 6dedf745..c645c70e 100644 --- a/wercker.yml +++ b/wercker.yml @@ -30,16 +30,18 @@ build: - script: name: go get code: | - go get + cd $WERCKER_SOURCE_DIR + go version + go get -t $(go list ./... | grep -v textencoding/glyphlist) # Build the project - script: name: go build code: | - go build $(go list ./... | grep -v examples) + go build $(go list ./... | grep -v examples | grep -v textencoding/glyphlist) # Test the project - script: name: go test code: | - go test $(go list ./... | grep -v examples) + go test $(go list ./... | grep -v examples| grep -v textencoding/glyphlist)