From ae264d26140c7f4239d977586e67b2742fe2c898 Mon Sep 17 00:00:00 2001 From: Adrian-George Bostan Date: Thu, 4 Oct 2018 19:45:39 +0300 Subject: [PATCH 01/24] Fix typo in the styled paragraph --- pdf/creator/styled_paragraph.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf/creator/styled_paragraph.go b/pdf/creator/styled_paragraph.go index e9cebbab..4e23abd2 100644 --- a/pdf/creator/styled_paragraph.go +++ b/pdf/creator/styled_paragraph.go @@ -281,7 +281,7 @@ func (p *StyledParagraph) wrapText() error { } // newline wrapping. - if glyph == "controllf" { + if glyph == "controlLF" { // moves to next line. line = append(line, TextChunk{ Text: strings.TrimRightFunc(string(part), unicode.IsSpace), From 35bd3543143ac7363af3295c4a2b1aeb772ec348 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Tue, 23 Oct 2018 14:16:02 +0000 Subject: [PATCH 02/24] Fix for #188 based on #186 and #187. Cleaned up and simplified according to style guide. --- pdf/core/primitives.go | 2 +- pdf/model/colorspace.go | 128 ++++++++++++++++++++-------------------- pdf/model/const.go | 4 +- 3 files changed, 69 insertions(+), 65 deletions(-) diff --git a/pdf/core/primitives.go b/pdf/core/primitives.go index d49dbea1..d0a0b97b 100644 --- a/pdf/core/primitives.go +++ b/pdf/core/primitives.go @@ -568,7 +568,7 @@ func TraceToDirectObject(obj PdfObject) PdfObject { iobj, isIndirectObj = obj.(*PdfIndirectObject) depth++ if depth > TraceMaxDepth { - common.Log.Error("Trace depth level beyond 20 - error!") + common.Log.Error("ERROR: Trace depth level beyond %d - not going deeper!", TraceMaxDepth) return nil } } diff --git a/pdf/model/colorspace.go b/pdf/model/colorspace.go index ce9ee44c..b9701a57 100644 --- a/pdf/model/colorspace.go +++ b/pdf/model/colorspace.go @@ -60,37 +60,42 @@ func NewPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) { var csName *PdfObjectName var csArray *PdfObjectArray - if indObj, is := obj.(*PdfIndirectObject); is { - if array, is := indObj.PdfObject.(*PdfObjectArray); is { - container = indObj - csArray = array - } else if name, is := indObj.PdfObject.(*PdfObjectName); is { - container = indObj - csName = name - } - } else if array, is := obj.(*PdfObjectArray); is { - csArray = array - } else if name, is := obj.(*PdfObjectName); is { - csName = name + if indObj, isInd := obj.(*PdfIndirectObject); isInd { + container = indObj + } + + /* + 8.6.3 p. 149 (PDF32000_2008): + A colour space shall be defined by an array object whose first element is a name object identifying the + colour space family. The remaining array elements, if any, are parameters that further characterize the + colour space; their number and types vary according to the particular family. + + For families that do not require parameters, the colour space may be specified simply by the family name + itself instead of an array. + */ + + obj = TraceToDirectObject(obj) + switch t := obj.(type) { + case *PdfObjectArray: + csArray = t + case *PdfObjectName: + csName = t } // If specified by a name directly: Device colorspace or Pattern. if csName != nil { - if *csName == "DeviceGray" { - cs := NewPdfColorspaceDeviceGray() - return cs, nil - } else if *csName == "DeviceRGB" { - cs := NewPdfColorspaceDeviceRGB() - return cs, nil - } else if *csName == "DeviceCMYK" { - cs := NewPdfColorspaceDeviceCMYK() - return cs, nil - } else if *csName == "Pattern" { - cs := NewPdfColorspaceSpecialPattern() - return cs, nil - } else { - common.Log.Error("Unknown colorspace %s", *csName) - return nil, errors.New("Unknown colorspace") + switch *csName { + case "DeviceGray": + return NewPdfColorspaceDeviceGray(), nil + case "DeviceRGB": + return NewPdfColorspaceDeviceRGB(), nil + case "DeviceCMYK": + return NewPdfColorspaceDeviceCMYK(), nil + case "Pattern": + return NewPdfColorspaceSpecialPattern(), nil + default: + common.Log.Debug("ERROR: Unknown colorspace %s", *csName) + return nil, ErrRangeError } } @@ -99,48 +104,45 @@ func NewPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) { if container == nil { csObject = csArray } - if name, is := (*csArray)[0].(*PdfObjectName); is { - if *name == "DeviceGray" && len(*csArray) == 1 { - cs := NewPdfColorspaceDeviceGray() - return cs, nil - } else if *name == "DeviceRGB" && len(*csArray) == 1 { - cs := NewPdfColorspaceDeviceRGB() - return cs, nil - } else if *name == "DeviceCMYK" && len(*csArray) == 1 { - cs := NewPdfColorspaceDeviceCMYK() - return cs, nil - } else if *name == "CalGray" { - cs, err := newPdfColorspaceCalGrayFromPdfObject(csObject) - return cs, err - } else if *name == "CalRGB" { - cs, err := newPdfColorspaceCalRGBFromPdfObject(csObject) - return cs, err - } else if *name == "Lab" { - cs, err := newPdfColorspaceLabFromPdfObject(csObject) - return cs, err - } else if *name == "ICCBased" { - cs, err := newPdfColorspaceICCBasedFromPdfObject(csObject) - return cs, err - } else if *name == "Pattern" { - cs, err := newPdfColorspaceSpecialPatternFromPdfObject(csObject) - return cs, err - } else if *name == "Indexed" { - cs, err := newPdfColorspaceSpecialIndexedFromPdfObject(csObject) - return cs, err - } else if *name == "Separation" { - cs, err := newPdfColorspaceSpecialSeparationFromPdfObject(csObject) - return cs, err - } else if *name == "DeviceN" { - cs, err := newPdfColorspaceDeviceNFromPdfObject(csObject) - return cs, err - } else { + firstEl := TraceToDirectObject((*csArray)[0]) + if name, isName := firstEl.(*PdfObjectName); isName { + switch *name { + case "DeviceGray": + if len(*csArray) == 1 { + return NewPdfColorspaceDeviceGray(), nil + } + case "DeviceRGB": + if len(*csArray) == 1 { + return NewPdfColorspaceDeviceRGB(), nil + } + case "DeviceCMYK": + if len(*csArray) == 1 { + return NewPdfColorspaceDeviceCMYK(), nil + } + case "CalGray": + return newPdfColorspaceCalGrayFromPdfObject(csObject) + case "CalRGB": + return newPdfColorspaceCalRGBFromPdfObject(csObject) + case "Lab": + return newPdfColorspaceLabFromPdfObject(csObject) + case "ICCBased": + return newPdfColorspaceICCBasedFromPdfObject(csObject) + case "Pattern": + return newPdfColorspaceSpecialPatternFromPdfObject(csObject) + case "Indexed": + return newPdfColorspaceSpecialIndexedFromPdfObject(csObject) + case "Separation": + return newPdfColorspaceSpecialSeparationFromPdfObject(csObject) + case "DeviceN": + return newPdfColorspaceDeviceNFromPdfObject(csObject) + default: common.Log.Debug("Array with invalid name: %s", *name) } } } common.Log.Debug("PDF File Error: Colorspace type error: %s", obj.String()) - return nil, errors.New("Type error") + return nil, ErrTypeCheck } // determine PDF colorspace from a PdfObject. Returns the colorspace name and an error on failure. diff --git a/pdf/model/const.go b/pdf/model/const.go index 1cf20ea6..7c671bb1 100644 --- a/pdf/model/const.go +++ b/pdf/model/const.go @@ -13,5 +13,7 @@ var ( ErrRequiredAttributeMissing = errors.New("Required attribute missing") ErrInvalidAttribute = errors.New("Invalid attribute") ErrTypeError = errors.New("Type check error") - ErrRangeError = errors.New("Range check error") + + // ErrRangeError typically occurs when an input parameter is out of range or has invalid value. + ErrRangeError = errors.New("Range check error") ) From c8e20cd51555b6f116ba498541f2f50be85c760f Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Tue, 23 Oct 2018 14:16:18 +0000 Subject: [PATCH 03/24] Jenkinsfile update - fix golint --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1c97cf8b..ed7dec8c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -16,7 +16,7 @@ node { stage('Prepare') { // Get linter and other build tools. - sh 'go get github.com/golang/lint/golint' + sh 'go get -u golang.org/x/lint/golint' sh 'go get github.com/tebeka/go2xunit' sh 'go get github.com/t-yuki/gocover-cobertura' From 748c3804bc8caf3ba8be2f0c74f7377bb3499a35 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Tue, 23 Oct 2018 14:20:14 +0000 Subject: [PATCH 04/24] comment style --- pdf/model/colorspace.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pdf/model/colorspace.go b/pdf/model/colorspace.go index b9701a57..7beacc9b 100644 --- a/pdf/model/colorspace.go +++ b/pdf/model/colorspace.go @@ -64,15 +64,13 @@ func NewPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) { container = indObj } - /* - 8.6.3 p. 149 (PDF32000_2008): - A colour space shall be defined by an array object whose first element is a name object identifying the - colour space family. The remaining array elements, if any, are parameters that further characterize the - colour space; their number and types vary according to the particular family. - - For families that do not require parameters, the colour space may be specified simply by the family name - itself instead of an array. - */ + // 8.6.3 p. 149 (PDF32000_2008): + // A colour space shall be defined by an array object whose first element is a name object identifying the + // colour space family. The remaining array elements, if any, are parameters that further characterize the + // colour space; their number and types vary according to the particular family. + // + // For families that do not require parameters, the colour space may be specified simply by the family name + // itself instead of an array. obj = TraceToDirectObject(obj) switch t := obj.(type) { From 75dbcacabefe88de713f3ab0759d17849ea961f4 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Tue, 23 Oct 2018 14:46:42 +0000 Subject: [PATCH 05/24] Support more full name params in inline images. Fixes #235 Added reference and cleaned up style. --- pdf/contentstream/encoding.go | 20 +++++++++++------- pdf/contentstream/inline-image.go | 35 ++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/pdf/contentstream/encoding.go b/pdf/contentstream/encoding.go index 115fc955..1b8e6126 100644 --- a/pdf/contentstream/encoding.go +++ b/pdf/contentstream/encoding.go @@ -54,19 +54,25 @@ func newEncoderFromInlineImage(inlineImage *ContentStreamInlineImage) (core.Stre } } - if *filterName == "AHx" { + // From Table 94 p. 224 (PDF32000_2008): + // Additional Abbreviations in an Inline Image Object: + + switch *filterName { + case "AHx", "ASCIIHexDecode": return core.NewASCIIHexEncoder(), nil - } else if *filterName == "A85" { + case "A85", "ASCII85Decode": return core.NewASCII85Encoder(), nil - } else if *filterName == "DCT" { + case "DCT", "DCTDecode": return newDCTEncoderFromInlineImage(inlineImage) - } else if *filterName == "Fl" { + case "Fl", "FlateDecode": return newFlateEncoderFromInlineImage(inlineImage, nil) - } else if *filterName == "LZW" { + case "LZW", "LZWDecode": return newLZWEncoderFromInlineImage(inlineImage, nil) - } else if *filterName == "CCF" { + case "CCF", "CCITTFaxDecode": return core.NewCCITTFaxEncoder(), nil - } else { + case "RL", "RunLengthDecode": + return core.NewRunLengthEncoder(), nil + default: 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 70f6c883..f5704509 100644 --- a/pdf/contentstream/inline-image.go +++ b/pdf/contentstream/inline-image.go @@ -173,7 +173,7 @@ func (this *ContentStreamInlineImage) GetColorSpace(resources *model.PdfPageReso return model.NewPdfColorspaceDeviceRGB(), nil } else if *name == "CMYK" || *name == "DeviceCMYK" { return model.NewPdfColorspaceDeviceCMYK(), nil - } else if *name == "I" { + } else if *name == "I" || *name == "Indexed" { return nil, errors.New("Unsupported Index colorspace") } else { if resources.ColorSpace == nil { @@ -327,27 +327,38 @@ func (this *ContentStreamParser) ParseInlineImage() (*ContentStreamInlineImage, return nil, fmt.Errorf("Not expecting an operand") } - if *param == "BPC" || *param == "BitsPerComponent" { + // From 8.9.7 "Inline Images" p. 223 (PDF32000_2008): + // The key-value pairs appearing between the BI and ID operators are analogous to those in the dictionary + // portion of an image XObject (though the syntax is different). + // Table 93 shows the entries that are valid for an inline image, all of which shall have the same meanings + // as in a stream dictionary (see Table 5) or an image dictionary (see Table 89). + // Entries other than those listed shall be ignored; in particular, the Type, Subtype, and Length + // entries normally found in a stream or image dictionary are unnecessary. + // For convenience, the abbreviations shown in the table may be used in place of the fully spelled-out keys. + // Table 94 shows additional abbreviations that can be used for the names of colour spaces and filters. + + switch *param { + case "BPC", "BitsPerComponent": im.BitsPerComponent = valueObj - } else if *param == "CS" || *param == "ColorSpace" { + case "CS", "ColorSpace": im.ColorSpace = valueObj - } else if *param == "D" || *param == "Decode" { + case "D", "Decode": im.Decode = valueObj - } else if *param == "DP" || *param == "DecodeParms" { + case "DP", "DecodeParms": im.DecodeParms = valueObj - } else if *param == "F" || *param == "Filter" { + case "F", "Filter": im.Filter = valueObj - } else if *param == "H" || *param == "Height" { + case "H", "Height": im.Height = valueObj - } else if *param == "IM" { + case "IM", "ImageMask": im.ImageMask = valueObj - } else if *param == "Intent" { + case "Intent": im.Intent = valueObj - } else if *param == "I" { + case "I", "Interpolate": im.Interpolate = valueObj - } else if *param == "W" || *param == "Width" { + case "W", "Width": im.Width = valueObj - } else { + default: return nil, fmt.Errorf("Unknown inline image parameter %s", *param) } } From 591c788f4b54454d7821fa047942a64f779d2f81 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Tue, 23 Oct 2018 14:56:12 +0000 Subject: [PATCH 06/24] Jenkinsfile - golint fix --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1c97cf8b..ed7dec8c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -16,7 +16,7 @@ node { stage('Prepare') { // Get linter and other build tools. - sh 'go get github.com/golang/lint/golint' + sh 'go get -u golang.org/x/lint/golint' sh 'go get github.com/tebeka/go2xunit' sh 'go get github.com/t-yuki/gocover-cobertura' From ea294c88179191fd729913486cb73c447c2097ea Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Fri, 9 Nov 2018 10:52:57 +0000 Subject: [PATCH 07/24] update readme --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index f2404d53..940f1d1b 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,7 @@ go get github.com/unidoc/unidoc/... ## Getting Rid of the Watermark - Get a License Out of the box - unidoc is unlicensed and outputs a watermark on all pages, perfect for prototyping. -To use unidoc in your projects, you need to get a license. We have 3 license types: - -* Community: For open source AGPLv3 projects -* Business Individual -* Business Unlimited +To use unidoc in your projects, you need to get a license. Get your license on [https://unidoc.io](https://unidoc.io). From 2752beb6f0cdbae03254246e8f26f1e3455518c8 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Sat, 17 Nov 2018 11:32:42 +0000 Subject: [PATCH 08/24] Release v2.2.0 --- common/version.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/version.go b/common/version.go index ce5fa222..3f9251a7 100644 --- a/common/version.go +++ b/common/version.go @@ -11,12 +11,12 @@ import ( ) const releaseYear = 2018 -const releaseMonth = 8 -const releaseDay = 14 -const releaseHour = 19 -const releaseMin = 40 +const releaseMonth = 11 +const releaseDay = 17 +const releaseHour = 11 +const releaseMin = 30 // Holds version information, when bumping this make sure to bump the released at stamp also. -const Version = "2.1.1" +const Version = "2.2.0" var ReleasedAt = time.Date(releaseYear, releaseMonth, releaseDay, releaseHour, releaseMin, 0, 0, time.UTC) From 6277dde6374e506b234f7a9d3d9083adb85d903e Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Thu, 6 Dec 2018 14:33:58 +0000 Subject: [PATCH 09/24] Update README.md --- README.md | 101 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 940f1d1b..97c485aa 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,26 @@ +# Version 3 - Upcoming + +Version 3 of UniDoc is currently in alpha. It marks multiple significant new major advancements as well as many fixes and enhancements: + +- [ ] Composite fonts supported and font handling has and is being completely revamped, including unicode support. +- [ ] Digital signing validation and signing +- [ ] Append mode +- [x] PDF compression and optimization of outputs with several options 1) combining duplicates, 2) compressed object streams, 3) image points per inch threshold, 4) image quality. +- [ ] Text extraction significantly improved in quality and support for vectorized (position-based) text extraction (XY) +- [x] Paragraph in creator handling multiple styles within the same paragraph +- [x] Invoice component for easy PDF invoice generation +- [x] Table of contents automatically generated +- [x] Encryption support refactored and AESv3 support added +- [x] Form field filling and form flattening with appearance generation +- [x] Getting form field values and listing +- [x] FDF merge + +Go give it a spin, checkout the `v3` branch of unidoc and `v3` branch of unidoc-examples: +- https://github.com/unidoc/unidoc/tree/v3 +- https://github.com/unidoc/unidoc-examples/tree/v3 + +--- + # UniDoc [UniDoc](http://unidoc.io) is a powerful 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. @@ -10,21 +33,40 @@ go get github.com/unidoc/unidoc/... ~~~ -## Getting Rid of the Watermark - Get a License -Out of the box - unidoc is unlicensed and outputs a watermark on all pages, perfect for prototyping. -To use unidoc in your projects, you need to get a license. +## Features +UniDoc has a powerful set of features both for reading, processing and writing PDF. +The following list describes some of the main features: -Get your license on [https://unidoc.io](https://unidoc.io). +- [x] Create PDF reports with easy interface +- [x] Create PDF invoices (v3) +- [x] Merge PDF pages +- [x] Merge page contents +- [x] Split PDF pages and change page order +- [x] Rotate pages +- [x] Extract text from PDF files +- [x] Extract images +- [x] Add images to pages +- [x] Compress and optimize PDF output (v3) +- [x] Draw watermark on PDF files +- [x] Advanced page manipulation (blocks/templates) +- [x] Load PDF templates and modify +- [x] Flatten forms and generate appearance streams (v3) +- [x] Fill out forms and FDF merging (v3) +- [x] Unlock PDF files / remove password +- [x] Protect PDF files with a password +- [ ] Digital signatures (v3) + + +## How can I convince myself and my boss to buy unidoc rather using a free alternative? + +The choice is yours. There are multiple respectable efforts out there that can do many good things. + +In unidoc, we work hard to provide production quality builds taking every detail into consideration and providing excellent support to our customers. See our [testimonials](https://unidoc.io) for example. + +Security. We take security very seriously and we restrict access to github.com/unidoc/unidoc repository with protected branches and only 2 of the founders have access and every commit is reviewed prior to being accepted. + +The money goes back into making unidoc better. We want to make the best possible product and in order to do that we need the best people to contribute. A large fraction of the profits made goes back into developing unidoc. That way we have been able to get many excellent people to work and contribute to unidoc that would not be able to contribute their work for free. -To load your license, simply do: -``` -unidocLicenseKey := "... your license here ..." -err := license.SetLicenseKey(unidocLicenseKey) -if err != nil { - fmt.Printf("Error loading license: %v\n", err) - os.Exit(1) -} -``` ## Examples @@ -37,6 +79,18 @@ Contact us if you need any specific examples. For reliability, we recommend using specific versions and the vendoring capability of golang. Check out the Releases section to see the tagged releases. + +## 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 and consulting + +Please email us at support@unidoc.io for any queries. + +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. + ## Licensing Information This library (UniDoc) has a dual license, a commercial one suitable for closed source projects and an @@ -58,16 +112,21 @@ These activities include: Please see [pricing](http://unidoc.io/pricing) to purchase a commercial license or contact sales at sales@unidoc.io for more info. -## Contributing +## Getting Rid of the Watermark - Get a License +Out of the box - unidoc is unlicensed and outputs a watermark on all pages, perfect for prototyping. +To use unidoc in your projects, you need to get a license. -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. +Get your license on [https://unidoc.io](https://unidoc.io). -## Support and consulting - -Please email us at support@unidoc.io for any queries. - -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. +To load your license, simply do: +``` +unidocLicenseKey := "... your license here ..." +err := license.SetLicenseKey(unidocLicenseKey) +if err != nil { + fmt.Printf("Error loading license: %v\n", err) + os.Exit(1) +} +``` [contributing]: CONTRIBUTING.md From bd3408040c2ab2e7b80c491236ec682b287589bf Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Thu, 6 Dec 2018 14:50:32 +0000 Subject: [PATCH 10/24] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97c485aa..31793003 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ In unidoc, we work hard to provide production quality builds taking every detail Security. We take security very seriously and we restrict access to github.com/unidoc/unidoc repository with protected branches and only 2 of the founders have access and every commit is reviewed prior to being accepted. -The money goes back into making unidoc better. We want to make the best possible product and in order to do that we need the best people to contribute. A large fraction of the profits made goes back into developing unidoc. That way we have been able to get many excellent people to work and contribute to unidoc that would not be able to contribute their work for free. +The profits are invested back into making unidoc better. We want to make the best possible product and in order to do that we need the best people to contribute. A large fraction of the profits made goes back into developing unidoc. That way we have been able to get many excellent people to work and contribute to unidoc that would not be able to contribute their work for free. ## Examples From e2d2510db2b3ce03269ff9900e4b526d13c33b55 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Sun, 16 Dec 2018 11:18:42 +0000 Subject: [PATCH 11/24] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31793003..25030040 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Version 3 of UniDoc is currently in alpha. It marks multiple significant new maj - [ ] Composite fonts supported and font handling has and is being completely revamped, including unicode support. - [ ] Digital signing validation and signing -- [ ] Append mode +- [x] Append mode - [x] PDF compression and optimization of outputs with several options 1) combining duplicates, 2) compressed object streams, 3) image points per inch threshold, 4) image quality. - [ ] Text extraction significantly improved in quality and support for vectorized (position-based) text extraction (XY) - [x] Paragraph in creator handling multiple styles within the same paragraph From 43fd7a1d52ac7d74f01447f50fcc902dea9f32bf Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Sun, 23 Dec 2018 16:48:07 +0000 Subject: [PATCH 12/24] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 25030040..3f3bfd76 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Version 3 of UniDoc is currently in alpha. It marks multiple significant new maj - [x] PDF compression and optimization of outputs with several options 1) combining duplicates, 2) compressed object streams, 3) image points per inch threshold, 4) image quality. - [ ] Text extraction significantly improved in quality and support for vectorized (position-based) text extraction (XY) - [x] Paragraph in creator handling multiple styles within the same paragraph -- [x] Invoice component for easy PDF invoice generation +- [x] [Invoice component for easy PDF invoice generation](https://unidoc.io/news/simple-invoices) - [x] Table of contents automatically generated - [x] Encryption support refactored and AESv3 support added - [x] Form field filling and form flattening with appearance generation From b0e69735ff8ee4b094777fe38e002447c9907d70 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Sun, 6 Jan 2019 01:41:23 +0000 Subject: [PATCH 13/24] Add package fjson for filling form data and filling via json --- pdf/fjson/doc.go | 7 ++ pdf/fjson/fielddata.go | 161 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 pdf/fjson/doc.go create mode 100644 pdf/fjson/fielddata.go diff --git a/pdf/fjson/doc.go b/pdf/fjson/doc.go new file mode 100644 index 00000000..b60e6fd6 --- /dev/null +++ b/pdf/fjson/doc.go @@ -0,0 +1,7 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'LICENSE.md', which is part of this source code package. + */ + +// Package fjson provides support for loading PDF form field data from JSON data/files. +package fjson diff --git a/pdf/fjson/fielddata.go b/pdf/fjson/fielddata.go new file mode 100644 index 00000000..f69c7241 --- /dev/null +++ b/pdf/fjson/fielddata.go @@ -0,0 +1,161 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'LICENSE.md', which is part of this source code package. + */ + +package fjson + +import ( + "encoding/json" + "io" + "io/ioutil" + "os" + + "github.com/unidoc/unidoc/common" + "github.com/unidoc/unidoc/pdf/core" + "github.com/unidoc/unidoc/pdf/model" +) + +// FieldData represents form field data loaded from JSON file. +type FieldData struct { + values []fieldValue +} + +// fieldValue represents a field name and value for a PDF form field. Options lists +// a list of allowed values if present. +type fieldValue struct { + Name string `json:"name"` + Value string `json:"value"` + Options []string `json:"options,omitempty"` +} + +// LoadJSON loads JSON form data from `r`. +func LoadJSON(r io.Reader) (*FieldData, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + var fdata FieldData + err = json.Unmarshal(data, &fdata.values) + if err != nil { + return nil, err + } + + return &fdata, nil +} + +// LoadJSONFromPath loads form field data from a JSON file. +func LoadJSONFromPath(jsonPath string) (*FieldData, error) { + f, err := os.Open(jsonPath) + if err != nil { + return nil, err + } + + return LoadJSON(f) +} + +// LoadPDF loads form field data from a PDF. +func LoadPDF(rs io.ReadSeeker) (*FieldData, error) { + pdfReader, err := model.NewPdfReader(rs) + if err != nil { + return nil, err + } + + if pdfReader.AcroForm == nil { + return nil, nil + } + + var fieldvals []fieldValue + fields := pdfReader.AcroForm.AllFields() + for _, f := range fields { + var options []string + optMap := make(map[string]struct{}) + + name, err := f.FullName() + if err != nil { + return nil, err + } + var val string + switch t := f.V.(type) { + case *core.PdfObjectString: + val = t.Decoded() + default: + common.Log.Debug("%s: Unsupported %T", name, t) + if len(f.Annotations) > 0 { + for _, wa := range f.Annotations { + state, found := core.GetName(wa.AS) + if found { + val = state.String() + } + + // Options are the keys in the N/D dictionaries in the AP appearance dict. + apDict, has := core.GetDict(wa.AP) + if has { + nDict, has := core.GetDict(apDict.Get("N")) + if has { + for _, key := range nDict.Keys() { + keystr := key.String() + if _, has := optMap[keystr]; !has { + options = append(options, keystr) + optMap[keystr] = struct{}{} + } + } + } + dDict, has := core.GetDict(apDict.Get("D")) + if has { + for _, key := range dDict.Keys() { + keystr := key.String() + if _, has := optMap[keystr]; !has { + options = append(options, keystr) + optMap[keystr] = struct{}{} + } + } + } + } + } + } + } + + fval := fieldValue{ + Name: name, + Value: val, + Options: options, + } + fieldvals = append(fieldvals, fval) + } + + fdata := FieldData{ + values: fieldvals, + } + + return &fdata, nil +} + +// LoadPDFfromPath loads form field data from a PDF file. +func LoadPDFFromPath(pdfPath string) (*FieldData, error) { + f, err := os.Open(pdfPath) + if err != nil { + return nil, err + } + defer f.Close() + + return LoadPDF(f) +} + +// JSON returns the field data as a string in JSON format. +func (fd FieldData) JSON() (string, error) { + data, err := json.MarshalIndent(fd.values, "", " ") + return string(data), err +} + +// FieldValues implements model.FieldValueProvider interface. +func (fd *FieldData) FieldValues() (map[string]core.PdfObject, error) { + fvalMap := make(map[string]core.PdfObject) + for _, fval := range fd.values { + if len(fval.Value) > 0 { + fvalMap[fval.Name] = core.MakeString(fval.Value) + } + } + return fvalMap, nil +} From 5e0b7c9bbfbb3c64c3ff074d52b1d90fe99b22ba Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Mon, 7 Jan 2019 00:41:26 +0000 Subject: [PATCH 14/24] Add basic tests --- pdf/fjson/fielddata_test.go | 129 +++++++++++++++++++++++++++++++ pdf/fjson/testdata/basicform.pdf | Bin 0 -> 73646 bytes pdf/fjson/testdata/formdata.json | 46 +++++++++++ 3 files changed, 175 insertions(+) create mode 100644 pdf/fjson/fielddata_test.go create mode 100644 pdf/fjson/testdata/basicform.pdf create mode 100644 pdf/fjson/testdata/formdata.json diff --git a/pdf/fjson/fielddata_test.go b/pdf/fjson/fielddata_test.go new file mode 100644 index 00000000..19973a82 --- /dev/null +++ b/pdf/fjson/fielddata_test.go @@ -0,0 +1,129 @@ +package fjson + +import ( + "bytes" + "encoding/json" + "os" + "strings" + "testing" + + "github.com/unidoc/unidoc/pdf/model" +) + +func TestLoadPDFFormData(t *testing.T) { + fdata, err := LoadPDFFromPath(`./testdata/basicform.pdf`) + if err != nil { + t.Fatalf("Error: %v", err) + } + + data, err := fdata.JSON() + if err != nil { + t.Fatalf("Error: %v", err) + } + + var fields []struct { + Name string `json:"name"` + Value string `json:"value"` + Options []string `json:"options"` + } + + err = json.Unmarshal([]byte(data), &fields) + if err != nil { + t.Fatalf("Error: %v", err) + } + + if len(fields) != 9 { + t.Fatalf("Should have 9 fields") + } + + // Check first field. + if fields[0].Name != "full_name" { + t.Fatalf("Incorrect field name (got %s)", fields[0].Name) + } + if fields[0].Value != "" { + t.Fatalf("Value not empty") + } + if len(fields[0].Options) != 0 { + t.Fatalf("Options not empty") + } + + // Check another field. + if fields[7].Name != "female" { + t.Fatalf("Incorrect field name (got %s)", fields[7].Name) + } + if fields[7].Value != "Off" { + t.Fatalf("Value not Off (got %s)", fields[7].Value) + } + if strings.Join(fields[7].Options, ", ") != "Off, Yes" { + t.Fatalf("Wrong options (got %#v)", fields[7].Options) + } +} + +// Tests loading JSON form data, filling in a form and loading the PDF form data and comparing the results. +func TestFillPDFForm(t *testing.T) { + fdata, err := LoadJSONFromPath(`./testdata/formdata.json`) + if err != nil { + t.Fatalf("Error: %v", err) + } + data, err := fdata.JSON() + if err != nil { + t.Fatalf("Error: %v", err) + } + + // Open pdf to fill. + var pdfReader *model.PdfReader + { + f, err := os.Open(`./testdata/basicform.pdf`) + if err != nil { + t.Fatalf("Error: %v", err) + } + defer f.Close() + pdfReader, err = model.NewPdfReader(f) + if err != nil { + t.Fatalf("Error: %v", err) + } + } + + err = pdfReader.AcroForm.Fill(fdata) + if err != nil { + t.Fatalf("Error: %v", err) + } + + // Write to buffer. + var buf bytes.Buffer + { + // TODO(gunnsth): Implement a simpler method for populating all pages from a reader. + pdfWriter := model.NewPdfWriter() + for i := range pdfReader.PageList { + err := pdfWriter.AddPage(pdfReader.PageList[i]) + if err != nil { + t.Fatalf("Error: %v", err) + } + } + err := pdfWriter.SetForms(pdfReader.AcroForm) + if err != nil { + t.Fatalf("Error: %v", err) + } + err = pdfWriter.Write(&buf) + if err != nil { + t.Fatalf("Error: %v", err) + } + } + + bufReader := bytes.NewReader(buf.Bytes()) + + // Read back. + fdata2, err := LoadPDF(bufReader) + if err != nil { + t.Fatalf("Error: %v", err) + } + + data2, err := fdata2.JSON() + if err != nil { + t.Fatalf("Error: %v", err) + } + + if data != data2 { + t.Fatalf("%s != %s", data, data2) + } +} diff --git a/pdf/fjson/testdata/basicform.pdf b/pdf/fjson/testdata/basicform.pdf new file mode 100644 index 0000000000000000000000000000000000000000..06a56b4ee76cebb96a9bc4f6094a2d5254c4aee3 GIT binary patch literal 73646 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 Date: Mon, 7 Jan 2019 01:01:11 +0000 Subject: [PATCH 15/24] Add missing header --- pdf/fjson/fielddata_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pdf/fjson/fielddata_test.go b/pdf/fjson/fielddata_test.go index 19973a82..e00ca51b 100644 --- a/pdf/fjson/fielddata_test.go +++ b/pdf/fjson/fielddata_test.go @@ -1,3 +1,8 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'LICENSE.md', which is part of this source code package. + */ + package fjson import ( From 4a0a5c2631b980d5bd39567bb9cd7f4ae41f303e Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Tue, 8 Jan 2019 23:37:52 +0300 Subject: [PATCH 16/24] readme: add missing "go" code block marker This enables proper syntax highlighting on GitHub. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f3bfd76..53b07d75 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ To use unidoc in your projects, you need to get a license. Get your license on [https://unidoc.io](https://unidoc.io). To load your license, simply do: -``` +```go unidocLicenseKey := "... your license here ..." err := license.SetLicenseKey(unidocLicenseKey) if err != nil { From c40496b12b692f902ebd3f4fa3409c44203e67b3 Mon Sep 17 00:00:00 2001 From: Alfred Hall Date: Thu, 10 Jan 2019 11:13:25 +0000 Subject: [PATCH 17/24] CLA for UniDoc --- CONTRIBUTING.md | 4 +++- README.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a32e458..0e84a8f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,5 @@ ## Contributor License Agreement -* Please sign the [Contributor License Agreement](https://docs.google.com/a/owlglobal.io/forms/d/1PfTjEAi67-x0JOTU45SDonJnWy1fWB_J1aopGss34bY/viewform) in order to have your changes merged. +[![CLA assistant](https://cla-assistant.io/readme/badge/unidoc/unidoc)](https://cla-assistant.io/unidoc/unidoc) + +All contributors are must sign a contributor license agreement before their code will be reviewed and merged. diff --git a/README.md b/README.md index 53b07d75..99bb5fc8 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,9 @@ Check out the Releases section to see the tagged releases. ## 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. +[![CLA assistant](https://cla-assistant.io/readme/badge/unidoc/unidoc)](https://cla-assistant.io/unidoc/unidoc) + +All contributors are must sign a contributor license agreement before their code will be reviewed and merged. ## Support and consulting From a6cfb62eeaa503272977fdc08a4df3a2ce781d26 Mon Sep 17 00:00:00 2001 From: Alfred Hall Date: Thu, 10 Jan 2019 11:22:42 +0000 Subject: [PATCH 18/24] Fixing grammar in CLA --- CONTRIBUTING.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e84a8f9..88f82387 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,4 +2,4 @@ [![CLA assistant](https://cla-assistant.io/readme/badge/unidoc/unidoc)](https://cla-assistant.io/unidoc/unidoc) -All contributors are must sign a contributor license agreement before their code will be reviewed and merged. +All contributors must sign a contributor license agreement before their code will be reviewed and merged. diff --git a/README.md b/README.md index 99bb5fc8..890b7226 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Check out the Releases section to see the tagged releases. [![CLA assistant](https://cla-assistant.io/readme/badge/unidoc/unidoc)](https://cla-assistant.io/unidoc/unidoc) -All contributors are must sign a contributor license agreement before their code will be reviewed and merged. +All contributors must sign a contributor license agreement before their code will be reviewed and merged. ## Support and consulting From 0bfde3f2245bdb4e71682380b74aec2ad247ad22 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Thu, 10 Jan 2019 21:43:38 +0000 Subject: [PATCH 19/24] Address PR comments --- pdf/core/primitives.go | 4 ++ pdf/fjson/fielddata.go | 114 +++++++++++++++++------------------- pdf/fjson/fielddata_test.go | 6 +- 3 files changed, 60 insertions(+), 64 deletions(-) diff --git a/pdf/core/primitives.go b/pdf/core/primitives.go index 0d36a5c2..9cf8aa2a 100644 --- a/pdf/core/primitives.go +++ b/pdf/core/primitives.go @@ -654,7 +654,11 @@ func (d *PdfObjectDictionary) GetString(key PdfObjectName) (string, bool) { } // Keys returns the list of keys in the dictionary. +// If `d` is nil returns a nil slice. func (d *PdfObjectDictionary) Keys() []PdfObjectName { + if d == nil { + return nil + } return d.keys } diff --git a/pdf/fjson/fielddata.go b/pdf/fjson/fielddata.go index f69c7241..06b6b8eb 100644 --- a/pdf/fjson/fielddata.go +++ b/pdf/fjson/fielddata.go @@ -8,10 +8,8 @@ package fjson import ( "encoding/json" "io" - "io/ioutil" "os" - "github.com/unidoc/unidoc/common" "github.com/unidoc/unidoc/pdf/core" "github.com/unidoc/unidoc/pdf/model" ) @@ -21,42 +19,38 @@ type FieldData struct { values []fieldValue } -// fieldValue represents a field name and value for a PDF form field. Options lists -// a list of allowed values if present. +// fieldValue represents a field name and value for a PDF form field. type fieldValue struct { - Name string `json:"name"` - Value string `json:"value"` + Name string `json:"name"` + Value string `json:"value"` + + // Options lists allowed values if present. Options []string `json:"options,omitempty"` } -// LoadJSON loads JSON form data from `r`. -func LoadJSON(r io.Reader) (*FieldData, error) { - data, err := ioutil.ReadAll(r) - if err != nil { - return nil, err - } - +// LoadFromJSON loads JSON form data from `r`. +func LoadFromJSON(r io.Reader) (*FieldData, error) { var fdata FieldData - err = json.Unmarshal(data, &fdata.values) + err := json.NewDecoder(r).Decode(&fdata.values) if err != nil { return nil, err } - return &fdata, nil } -// LoadJSONFromPath loads form field data from a JSON file. -func LoadJSONFromPath(jsonPath string) (*FieldData, error) { - f, err := os.Open(jsonPath) +// LoadFromJSONFile loads form field data from a JSON file. +func LoadFromJSONFile(filePath string) (*FieldData, error) { + f, err := os.Open(filePath) if err != nil { return nil, err } + defer f.Close() - return LoadJSON(f) + return LoadFromJSON(f) } -// LoadPDF loads form field data from a PDF. -func LoadPDF(rs io.ReadSeeker) (*FieldData, error) { +// LoadFromPDF loads form field data from a PDF. +func LoadFromPDF(rs io.ReadSeeker) (*FieldData, error) { pdfReader, err := model.NewPdfReader(rs) if err != nil { return nil, err @@ -76,43 +70,41 @@ func LoadPDF(rs io.ReadSeeker) (*FieldData, error) { if err != nil { return nil, err } - var val string - switch t := f.V.(type) { - case *core.PdfObjectString: - val = t.Decoded() - default: - common.Log.Debug("%s: Unsupported %T", name, t) - if len(f.Annotations) > 0 { - for _, wa := range f.Annotations { - state, found := core.GetName(wa.AS) - if found { - val = state.String() - } - // Options are the keys in the N/D dictionaries in the AP appearance dict. - apDict, has := core.GetDict(wa.AP) - if has { - nDict, has := core.GetDict(apDict.Get("N")) - if has { - for _, key := range nDict.Keys() { - keystr := key.String() - if _, has := optMap[keystr]; !has { - options = append(options, keystr) - optMap[keystr] = struct{}{} - } - } - } - dDict, has := core.GetDict(apDict.Get("D")) - if has { - for _, key := range dDict.Keys() { - keystr := key.String() - if _, has := optMap[keystr]; !has { - options = append(options, keystr) - optMap[keystr] = struct{}{} - } - } - } - } + if t, ok := f.V.(*core.PdfObjectString); ok { + fieldvals = append(fieldvals, fieldValue{ + Name: name, + Value: t.Decoded(), + }) + continue + } + + var val string + for _, wa := range f.Annotations { + state, found := core.GetName(wa.AS) + if found { + val = state.String() + } + + // Options are the keys in the N/D dictionaries in the AP appearance dict. + apDict, has := core.GetDict(wa.AP) + if !has { + continue + } + nDict, _ := core.GetDict(apDict.Get("N")) + for _, key := range nDict.Keys() { + keystr := key.String() + if _, has := optMap[keystr]; !has { + options = append(options, keystr) + optMap[keystr] = struct{}{} + } + } + dDict, _ := core.GetDict(apDict.Get("D")) + for _, key := range dDict.Keys() { + keystr := key.String() + if _, has := optMap[keystr]; !has { + options = append(options, keystr) + optMap[keystr] = struct{}{} } } } @@ -132,15 +124,15 @@ func LoadPDF(rs io.ReadSeeker) (*FieldData, error) { return &fdata, nil } -// LoadPDFfromPath loads form field data from a PDF file. -func LoadPDFFromPath(pdfPath string) (*FieldData, error) { - f, err := os.Open(pdfPath) +// LoadFromPDFFile loads form field data from a PDF file. +func LoadFromPDFFile(filePath string) (*FieldData, error) { + f, err := os.Open(filePath) if err != nil { return nil, err } defer f.Close() - return LoadPDF(f) + return LoadFromPDF(f) } // JSON returns the field data as a string in JSON format. diff --git a/pdf/fjson/fielddata_test.go b/pdf/fjson/fielddata_test.go index e00ca51b..7a93c1f1 100644 --- a/pdf/fjson/fielddata_test.go +++ b/pdf/fjson/fielddata_test.go @@ -16,7 +16,7 @@ import ( ) func TestLoadPDFFormData(t *testing.T) { - fdata, err := LoadPDFFromPath(`./testdata/basicform.pdf`) + fdata, err := LoadFromPDFFile(`./testdata/basicform.pdf`) if err != nil { t.Fatalf("Error: %v", err) } @@ -66,7 +66,7 @@ func TestLoadPDFFormData(t *testing.T) { // Tests loading JSON form data, filling in a form and loading the PDF form data and comparing the results. func TestFillPDFForm(t *testing.T) { - fdata, err := LoadJSONFromPath(`./testdata/formdata.json`) + fdata, err := LoadFromJSONFile(`./testdata/formdata.json`) if err != nil { t.Fatalf("Error: %v", err) } @@ -118,7 +118,7 @@ func TestFillPDFForm(t *testing.T) { bufReader := bytes.NewReader(buf.Bytes()) // Read back. - fdata2, err := LoadPDF(bufReader) + fdata2, err := LoadFromPDF(bufReader) if err != nil { t.Fatalf("Error: %v", err) } From cd9a2c45e9a0791bae9f4f3f5e9bba77a04d774e Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Thu, 10 Jan 2019 23:28:38 +0000 Subject: [PATCH 20/24] Update version --- common/version.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/version.go b/common/version.go index 1c0fa11e..39842f4d 100644 --- a/common/version.go +++ b/common/version.go @@ -10,13 +10,13 @@ import ( "time" ) -const releaseYear = 2018 -const releaseMonth = 8 -const releaseDay = 14 -const releaseHour = 19 -const releaseMin = 40 +const releaseYear = 2019 +const releaseMonth = 1 +const releaseDay = 10 +const releaseHour = 22 +const releaseMin = 42 // Version holds version information, when bumping this make sure to bump the released at stamp also. -const Version = "2.1.1" +const Version = "3.0.0-alpha.1" var ReleasedAt = time.Date(releaseYear, releaseMonth, releaseDay, releaseHour, releaseMin, 0, 0, time.UTC) From f56ca6aae3dfc33abbc08e4a4db2c2f0dece19f8 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Fri, 11 Jan 2019 00:02:30 +0000 Subject: [PATCH 21/24] Remove dot import of core in colorspace.go --- pdf/model/colorspace.go | 444 ++++++++++++++++++++-------------------- pdf/model/const.go | 15 +- 2 files changed, 230 insertions(+), 229 deletions(-) diff --git a/pdf/model/colorspace.go b/pdf/model/colorspace.go index 4c560000..d7867797 100644 --- a/pdf/model/colorspace.go +++ b/pdf/model/colorspace.go @@ -11,7 +11,7 @@ import ( "math" "github.com/unidoc/unidoc/common" - . "github.com/unidoc/unidoc/pdf/core" + "github.com/unidoc/unidoc/pdf/core" ) // PdfColorspace interface defines the common methods of a PDF colorspace. @@ -46,10 +46,10 @@ type PdfColorspace interface { // GetNumComponents returns the number of components in the PdfColorspace. GetNumComponents() int // ToPdfObject returns a PdfObject representation of the PdfColorspace. - ToPdfObject() PdfObject - // ColorFromPdfObjects returns a PdfColor in the given PdfColorspace from an array of PdfObject's where each + ToPdfObject() core.PdfObject + // ColorFromPdfObjects returns a PdfColor in the given PdfColorspace from an array of PdfObject where each // PdfObject represents a numeric value. - ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) + ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) // ColorFromFloats returns a new PdfColor based on input color components for a given PdfColorspace. ColorFromFloats(vals []float64) (PdfColor, error) // DecodeArray returns the Decode array for the PdfColorSpace, i.e. the range of each component. @@ -62,12 +62,12 @@ type PdfColor interface { // NewPdfColorspaceFromPdfObject loads a PdfColorspace from a PdfObject. Returns an error if there is // a failure in loading. -func NewPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) { - var container *PdfIndirectObject - var csName *PdfObjectName - var csArray *PdfObjectArray +func NewPdfColorspaceFromPdfObject(obj core.PdfObject) (PdfColorspace, error) { + var container *core.PdfIndirectObject + var csName *core.PdfObjectName + var csArray *core.PdfObjectArray - if indObj, isInd := obj.(*PdfIndirectObject); isInd { + if indObj, isInd := obj.(*core.PdfIndirectObject); isInd { container = indObj } @@ -79,11 +79,11 @@ func NewPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) { // For families that do not require parameters, the colour space may be specified simply by the family name // itself instead of an array. - obj = TraceToDirectObject(obj) + obj = core.TraceToDirectObject(obj) switch t := obj.(type) { - case *PdfObjectArray: + case *core.PdfObjectArray: csArray = t - case *PdfObjectName: + case *core.PdfObjectName: csName = t } @@ -100,16 +100,16 @@ func NewPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) { return NewPdfColorspaceSpecialPattern(), nil default: common.Log.Debug("ERROR: Unknown colorspace %s", *csName) - return nil, ErrRangeError + return nil, errRangeError } } if csArray != nil && csArray.Len() > 0 { - var csObject PdfObject = container + var csObject core.PdfObject = container if container == nil { csObject = csArray } - if name, found := GetName(csArray.Get(0)); found { + if name, found := core.GetName(csArray.Get(0)); found { switch name.String() { case "DeviceGray": if csArray.Len() == 1 { @@ -151,19 +151,19 @@ func NewPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) { // DetermineColorspaceNameFromPdfObject determines 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 +func DetermineColorspaceNameFromPdfObject(obj core.PdfObject) (core.PdfObjectName, error) { + var csName *core.PdfObjectName + var csArray *core.PdfObjectArray - if indObj, is := obj.(*PdfIndirectObject); is { - if array, is := indObj.PdfObject.(*PdfObjectArray); is { + if indObj, is := obj.(*core.PdfIndirectObject); is { + if array, is := indObj.PdfObject.(*core.PdfObjectArray); is { csArray = array - } else if name, is := indObj.PdfObject.(*PdfObjectName); is { + } else if name, is := indObj.PdfObject.(*core.PdfObjectName); is { csName = name } - } else if array, is := obj.(*PdfObjectArray); is { + } else if array, is := obj.(*core.PdfObjectArray); is { csArray = array - } else if name, is := obj.(*PdfObjectName); is { + } else if name, is := obj.(*core.PdfObjectName); is { csName = name } @@ -178,7 +178,7 @@ func DetermineColorspaceNameFromPdfObject(obj PdfObject) (PdfObjectName, error) } if csArray != nil && csArray.Len() > 0 { - if name, is := csArray.Get(0).(*PdfObjectName); is { + if name, is := csArray.Get(0).(*core.PdfObjectName); is { switch *name { case "DeviceGray", "DeviceRGB", "DeviceCMYK": if csArray.Len() == 1 { @@ -238,8 +238,8 @@ func (cs *PdfColorspaceDeviceGray) DecodeArray() []float64 { return []float64{0, 1.0} } -func (cs *PdfColorspaceDeviceGray) ToPdfObject() PdfObject { - return MakeName("DeviceGray") +func (cs *PdfColorspaceDeviceGray) ToPdfObject() core.PdfObject { + return core.MakeName("DeviceGray") } func (cs *PdfColorspaceDeviceGray) String() string { @@ -267,12 +267,12 @@ func (cs *PdfColorspaceDeviceGray) ColorFromFloats(vals []float64) (PdfColor, er return NewPdfColorDeviceGray(val), nil } -func (cs *PdfColorspaceDeviceGray) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceDeviceGray) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != 1 { return nil, errors.New("range check") } - floats, err := GetNumbersAsFloat(objects) + floats, err := core.GetNumbersAsFloat(objects) if err != nil { return nil, err } @@ -378,8 +378,8 @@ func (cs *PdfColorspaceDeviceRGB) DecodeArray() []float64 { return []float64{0.0, 1.0, 0.0, 1.0, 0.0, 1.0} } -func (cs *PdfColorspaceDeviceRGB) ToPdfObject() PdfObject { - return MakeName("DeviceRGB") +func (cs *PdfColorspaceDeviceRGB) ToPdfObject() core.PdfObject { + return core.MakeName("DeviceRGB") } func (cs *PdfColorspaceDeviceRGB) ColorFromFloats(vals []float64) (PdfColor, error) { @@ -411,12 +411,12 @@ func (cs *PdfColorspaceDeviceRGB) ColorFromFloats(vals []float64) (PdfColor, err } // ColorFromPdfObjects gets the color from a series of pdf objects (3 for rgb). -func (cs *PdfColorspaceDeviceRGB) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceDeviceRGB) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != 3 { return nil, errors.New("range check") } - floats, err := GetNumbersAsFloat(objects) + floats, err := core.GetNumbersAsFloat(objects) if err != nil { return nil, err } @@ -524,8 +524,8 @@ func (cs *PdfColorspaceDeviceCMYK) DecodeArray() []float64 { return []float64{0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0} } -func (cs *PdfColorspaceDeviceCMYK) ToPdfObject() PdfObject { - return MakeName("DeviceCMYK") +func (cs *PdfColorspaceDeviceCMYK) ToPdfObject() core.PdfObject { + return core.MakeName("DeviceCMYK") } func (cs *PdfColorspaceDeviceCMYK) ColorFromFloats(vals []float64) (PdfColor, error) { @@ -562,12 +562,12 @@ func (cs *PdfColorspaceDeviceCMYK) ColorFromFloats(vals []float64) (PdfColor, er } // ColorFromPdfObjects gets the color from a series of pdf objects (4 for cmyk). -func (cs *PdfColorspaceDeviceCMYK) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceDeviceCMYK) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != 4 { return nil, errors.New("range check") } - floats, err := GetNumbersAsFloat(objects) + floats, err := core.GetNumbersAsFloat(objects) if err != nil { return nil, err } @@ -689,7 +689,7 @@ type PdfColorspaceCalGray struct { BlackPoint []float64 // [XB, YB, ZB] Gamma float64 - container *PdfIndirectObject + container *core.PdfIndirectObject } func NewPdfColorspaceCalGray() *PdfColorspaceCalGray { @@ -715,18 +715,18 @@ func (cs *PdfColorspaceCalGray) DecodeArray() []float64 { return []float64{0.0, 1.0} } -func newPdfColorspaceCalGrayFromPdfObject(obj PdfObject) (*PdfColorspaceCalGray, error) { +func newPdfColorspaceCalGrayFromPdfObject(obj core.PdfObject) (*PdfColorspaceCalGray, error) { cs := NewPdfColorspaceCalGray() // If within an indirect object, then make a note of it. If we write out the PdfObject later // we can reference the same container. Otherwise is not within a container, but rather // a new array. - if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect { + if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { cs.container = indObj } - obj = TraceToDirectObject(obj) - array, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + array, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("type error") } @@ -736,8 +736,8 @@ func newPdfColorspaceCalGrayFromPdfObject(obj PdfObject) (*PdfColorspaceCalGray, } // Name. - obj = TraceToDirectObject(array.Get(0)) - name, ok := obj.(*PdfObjectName) + obj = core.TraceToDirectObject(array.Get(0)) + name, ok := obj.(*core.PdfObjectName) if !ok { return nil, fmt.Errorf("CalGray name not a Name object") } @@ -746,16 +746,16 @@ func newPdfColorspaceCalGrayFromPdfObject(obj PdfObject) (*PdfColorspaceCalGray, } // Dict. - obj = TraceToDirectObject(array.Get(1)) - dict, ok := obj.(*PdfObjectDictionary) + obj = core.TraceToDirectObject(array.Get(1)) + dict, ok := obj.(*core.PdfObjectDictionary) if !ok { return nil, fmt.Errorf("CalGray dict not a Dictionary object") } // WhitePoint (Required): [Xw, Yw, Zw] obj = dict.Get("WhitePoint") - obj = TraceToDirectObject(obj) - whitePointArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + whitePointArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("CalGray: Invalid WhitePoint") } @@ -771,8 +771,8 @@ func newPdfColorspaceCalGrayFromPdfObject(obj PdfObject) (*PdfColorspaceCalGray, // BlackPoint (Optional) obj = dict.Get("BlackPoint") if obj != nil { - obj = TraceToDirectObject(obj) - blackPointArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + blackPointArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("CalGray: Invalid BlackPoint") } @@ -789,8 +789,8 @@ func newPdfColorspaceCalGrayFromPdfObject(obj PdfObject) (*PdfColorspaceCalGray, // Gamma (Optional) obj = dict.Get("Gamma") if obj != nil { - obj = TraceToDirectObject(obj) - gamma, err := GetNumberAsFloat(obj) + obj = core.TraceToDirectObject(obj) + gamma, err := core.GetNumberAsFloat(obj) if err != nil { return nil, fmt.Errorf("CalGray: gamma not a number") } @@ -801,24 +801,24 @@ func newPdfColorspaceCalGrayFromPdfObject(obj PdfObject) (*PdfColorspaceCalGray, } // ToPdfObject return the CalGray colorspace as a PDF object (name dictionary). -func (cs *PdfColorspaceCalGray) ToPdfObject() PdfObject { +func (cs *PdfColorspaceCalGray) ToPdfObject() core.PdfObject { // CalGray color space dictionary.. - cspace := &PdfObjectArray{} + cspace := &core.PdfObjectArray{} - cspace.Append(MakeName("CalGray")) + cspace.Append(core.MakeName("CalGray")) - dict := MakeDict() + dict := core.MakeDict() if cs.WhitePoint != nil { - dict.Set("WhitePoint", MakeArray(MakeFloat(cs.WhitePoint[0]), MakeFloat(cs.WhitePoint[1]), MakeFloat(cs.WhitePoint[2]))) + dict.Set("WhitePoint", core.MakeArray(core.MakeFloat(cs.WhitePoint[0]), core.MakeFloat(cs.WhitePoint[1]), core.MakeFloat(cs.WhitePoint[2]))) } else { common.Log.Error("CalGray: Missing WhitePoint (Required)") } if cs.BlackPoint != nil { - dict.Set("BlackPoint", MakeArray(MakeFloat(cs.BlackPoint[0]), MakeFloat(cs.BlackPoint[1]), MakeFloat(cs.BlackPoint[2]))) + dict.Set("BlackPoint", core.MakeArray(core.MakeFloat(cs.BlackPoint[0]), core.MakeFloat(cs.BlackPoint[1]), core.MakeFloat(cs.BlackPoint[2]))) } - dict.Set("Gamma", MakeFloat(cs.Gamma)) + dict.Set("Gamma", core.MakeFloat(cs.Gamma)) cspace.Append(dict) if cs.container != nil { @@ -843,12 +843,12 @@ func (cs *PdfColorspaceCalGray) ColorFromFloats(vals []float64) (PdfColor, error return color, nil } -func (cs *PdfColorspaceCalGray) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceCalGray) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != 1 { return nil, errors.New("range check") } - floats, err := GetNumbersAsFloat(objects) + floats, err := core.GetNumbersAsFloat(objects) if err != nil { return nil, err } @@ -966,9 +966,9 @@ type PdfColorspaceCalRGB struct { BlackPoint []float64 Gamma []float64 Matrix []float64 // [XA YA ZA XB YB ZB XC YC ZC] ; default value identity [1 0 0 0 1 0 0 0 1] - dict *PdfObjectDictionary + dict *core.PdfObjectDictionary - container *PdfIndirectObject + container *core.PdfIndirectObject } func NewPdfColorspaceCalRGB() *PdfColorspaceCalRGB { @@ -996,15 +996,15 @@ func (cs *PdfColorspaceCalRGB) DecodeArray() []float64 { return []float64{0.0, 1.0, 0.0, 1.0, 0.0, 1.0} } -func newPdfColorspaceCalRGBFromPdfObject(obj PdfObject) (*PdfColorspaceCalRGB, error) { +func newPdfColorspaceCalRGBFromPdfObject(obj core.PdfObject) (*PdfColorspaceCalRGB, error) { cs := NewPdfColorspaceCalRGB() - if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect { + if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { cs.container = indObj } - obj = TraceToDirectObject(obj) - array, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + array, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("type error") } @@ -1014,8 +1014,8 @@ func newPdfColorspaceCalRGBFromPdfObject(obj PdfObject) (*PdfColorspaceCalRGB, e } // Name. - obj = TraceToDirectObject(array.Get(0)) - name, ok := obj.(*PdfObjectName) + obj = core.TraceToDirectObject(array.Get(0)) + name, ok := obj.(*core.PdfObjectName) if !ok { return nil, fmt.Errorf("CalRGB name not a Name object") } @@ -1024,16 +1024,16 @@ func newPdfColorspaceCalRGBFromPdfObject(obj PdfObject) (*PdfColorspaceCalRGB, e } // Dict. - obj = TraceToDirectObject(array.Get(1)) - dict, ok := obj.(*PdfObjectDictionary) + obj = core.TraceToDirectObject(array.Get(1)) + dict, ok := obj.(*core.PdfObjectDictionary) if !ok { return nil, fmt.Errorf("CalRGB name not a Name object") } // WhitePoint (Required): [Xw, Yw, Zw] obj = dict.Get("WhitePoint") - obj = TraceToDirectObject(obj) - whitePointArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + whitePointArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("CalRGB: Invalid WhitePoint") } @@ -1049,8 +1049,8 @@ func newPdfColorspaceCalRGBFromPdfObject(obj PdfObject) (*PdfColorspaceCalRGB, e // BlackPoint (Optional) obj = dict.Get("BlackPoint") if obj != nil { - obj = TraceToDirectObject(obj) - blackPointArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + blackPointArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("CalRGB: Invalid BlackPoint") } @@ -1067,8 +1067,8 @@ func newPdfColorspaceCalRGBFromPdfObject(obj PdfObject) (*PdfColorspaceCalRGB, e // Gamma (Optional) obj = dict.Get("Gamma") if obj != nil { - obj = TraceToDirectObject(obj) - gammaArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + gammaArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("CalRGB: Invalid Gamma") } @@ -1085,8 +1085,8 @@ func newPdfColorspaceCalRGBFromPdfObject(obj PdfObject) (*PdfColorspaceCalRGB, e // Matrix (Optional). obj = dict.Get("Matrix") if obj != nil { - obj = TraceToDirectObject(obj) - matrixArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + matrixArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("CalRGB: Invalid Matrix") } @@ -1105,32 +1105,32 @@ func newPdfColorspaceCalRGBFromPdfObject(obj PdfObject) (*PdfColorspaceCalRGB, e } // ToPdfObject returns colorspace in a PDF object format [name dictionary] -func (cs *PdfColorspaceCalRGB) ToPdfObject() PdfObject { +func (cs *PdfColorspaceCalRGB) ToPdfObject() core.PdfObject { // CalRGB color space dictionary.. - cspace := &PdfObjectArray{} + cspace := &core.PdfObjectArray{} - cspace.Append(MakeName("CalRGB")) + cspace.Append(core.MakeName("CalRGB")) - dict := MakeDict() + dict := core.MakeDict() if cs.WhitePoint != nil { - wp := MakeArray(MakeFloat(cs.WhitePoint[0]), MakeFloat(cs.WhitePoint[1]), MakeFloat(cs.WhitePoint[2])) + wp := core.MakeArray(core.MakeFloat(cs.WhitePoint[0]), core.MakeFloat(cs.WhitePoint[1]), core.MakeFloat(cs.WhitePoint[2])) dict.Set("WhitePoint", wp) } else { common.Log.Error("CalRGB: Missing WhitePoint (Required)") } if cs.BlackPoint != nil { - bp := MakeArray(MakeFloat(cs.BlackPoint[0]), MakeFloat(cs.BlackPoint[1]), MakeFloat(cs.BlackPoint[2])) + bp := core.MakeArray(core.MakeFloat(cs.BlackPoint[0]), core.MakeFloat(cs.BlackPoint[1]), core.MakeFloat(cs.BlackPoint[2])) dict.Set("BlackPoint", bp) } if cs.Gamma != nil { - g := MakeArray(MakeFloat(cs.Gamma[0]), MakeFloat(cs.Gamma[1]), MakeFloat(cs.Gamma[2])) + g := core.MakeArray(core.MakeFloat(cs.Gamma[0]), core.MakeFloat(cs.Gamma[1]), core.MakeFloat(cs.Gamma[2])) dict.Set("Gamma", g) } if cs.Matrix != nil { - matrix := MakeArray(MakeFloat(cs.Matrix[0]), MakeFloat(cs.Matrix[1]), MakeFloat(cs.Matrix[2]), - MakeFloat(cs.Matrix[3]), MakeFloat(cs.Matrix[4]), MakeFloat(cs.Matrix[5]), - MakeFloat(cs.Matrix[6]), MakeFloat(cs.Matrix[7]), MakeFloat(cs.Matrix[8])) + matrix := core.MakeArray(core.MakeFloat(cs.Matrix[0]), core.MakeFloat(cs.Matrix[1]), core.MakeFloat(cs.Matrix[2]), + core.MakeFloat(cs.Matrix[3]), core.MakeFloat(cs.Matrix[4]), core.MakeFloat(cs.Matrix[5]), + core.MakeFloat(cs.Matrix[6]), core.MakeFloat(cs.Matrix[7]), core.MakeFloat(cs.Matrix[8])) dict.Set("Matrix", matrix) } cspace.Append(dict) @@ -1170,12 +1170,12 @@ func (cs *PdfColorspaceCalRGB) ColorFromFloats(vals []float64) (PdfColor, error) return color, nil } -func (cs *PdfColorspaceCalRGB) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceCalRGB) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != 3 { return nil, errors.New("range check") } - floats, err := GetNumbersAsFloat(objects) + floats, err := core.GetNumbersAsFloat(objects) if err != nil { return nil, err } @@ -1299,7 +1299,7 @@ type PdfColorspaceLab struct { BlackPoint []float64 Range []float64 // [amin amax bmin bmax] - container *PdfIndirectObject + container *core.PdfIndirectObject } func (cs *PdfColorspaceLab) String() string { @@ -1336,15 +1336,15 @@ func NewPdfColorspaceLab() *PdfColorspaceLab { return cs } -func newPdfColorspaceLabFromPdfObject(obj PdfObject) (*PdfColorspaceLab, error) { +func newPdfColorspaceLabFromPdfObject(obj core.PdfObject) (*PdfColorspaceLab, error) { cs := NewPdfColorspaceLab() - if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect { + if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { cs.container = indObj } - obj = TraceToDirectObject(obj) - array, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + array, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("type error") } @@ -1354,8 +1354,8 @@ func newPdfColorspaceLabFromPdfObject(obj PdfObject) (*PdfColorspaceLab, error) } // Name. - obj = TraceToDirectObject(array.Get(0)) - name, ok := obj.(*PdfObjectName) + obj = core.TraceToDirectObject(array.Get(0)) + name, ok := obj.(*core.PdfObjectName) if !ok { return nil, fmt.Errorf("lab name not a Name object") } @@ -1364,16 +1364,16 @@ func newPdfColorspaceLabFromPdfObject(obj PdfObject) (*PdfColorspaceLab, error) } // Dict. - obj = TraceToDirectObject(array.Get(1)) - dict, ok := obj.(*PdfObjectDictionary) + obj = core.TraceToDirectObject(array.Get(1)) + dict, ok := obj.(*core.PdfObjectDictionary) if !ok { return nil, fmt.Errorf("colorspace dictionary missing or invalid") } // WhitePoint (Required): [Xw, Yw, Zw] obj = dict.Get("WhitePoint") - obj = TraceToDirectObject(obj) - whitePointArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + whitePointArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("Lab Invalid WhitePoint") } @@ -1389,8 +1389,8 @@ func newPdfColorspaceLabFromPdfObject(obj PdfObject) (*PdfColorspaceLab, error) // BlackPoint (Optional) obj = dict.Get("BlackPoint") if obj != nil { - obj = TraceToDirectObject(obj) - blackPointArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + blackPointArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("Lab: Invalid BlackPoint") } @@ -1407,8 +1407,8 @@ func newPdfColorspaceLabFromPdfObject(obj PdfObject) (*PdfColorspaceLab, error) // Range (Optional) obj = dict.Get("Range") if obj != nil { - obj = TraceToDirectObject(obj) - rangeArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + rangeArray, ok := obj.(*core.PdfObjectArray) if !ok { common.Log.Error("Range type error") return nil, fmt.Errorf("Lab: Type error") @@ -1428,27 +1428,27 @@ func newPdfColorspaceLabFromPdfObject(obj PdfObject) (*PdfColorspaceLab, error) } // ToPdfObject returns colorspace in a PDF object format [name dictionary] -func (cs *PdfColorspaceLab) ToPdfObject() PdfObject { +func (cs *PdfColorspaceLab) ToPdfObject() core.PdfObject { // CalRGB color space dictionary.. - csObj := MakeArray() + csObj := core.MakeArray() - csObj.Append(MakeName("Lab")) + csObj.Append(core.MakeName("Lab")) - dict := MakeDict() + dict := core.MakeDict() if cs.WhitePoint != nil { - wp := MakeArray(MakeFloat(cs.WhitePoint[0]), MakeFloat(cs.WhitePoint[1]), MakeFloat(cs.WhitePoint[2])) + wp := core.MakeArray(core.MakeFloat(cs.WhitePoint[0]), core.MakeFloat(cs.WhitePoint[1]), core.MakeFloat(cs.WhitePoint[2])) dict.Set("WhitePoint", wp) } else { common.Log.Error("Lab: Missing WhitePoint (Required)") } if cs.BlackPoint != nil { - bp := MakeArray(MakeFloat(cs.BlackPoint[0]), MakeFloat(cs.BlackPoint[1]), MakeFloat(cs.BlackPoint[2])) + bp := core.MakeArray(core.MakeFloat(cs.BlackPoint[0]), core.MakeFloat(cs.BlackPoint[1]), core.MakeFloat(cs.BlackPoint[2])) dict.Set("BlackPoint", bp) } if cs.Range != nil { - val := MakeArray(MakeFloat(cs.Range[0]), MakeFloat(cs.Range[1]), MakeFloat(cs.Range[2]), MakeFloat(cs.Range[3])) + val := core.MakeArray(core.MakeFloat(cs.Range[0]), core.MakeFloat(cs.Range[1]), core.MakeFloat(cs.Range[2]), core.MakeFloat(cs.Range[3])) dict.Set("Range", val) } csObj.Append(dict) @@ -1503,12 +1503,12 @@ func (cs *PdfColorspaceLab) ColorFromFloats(vals []float64) (PdfColor, error) { return color, nil } -func (cs *PdfColorspaceLab) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceLab) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != 3 { return nil, errors.New("range check") } - floats, err := GetNumbersAsFloat(objects) + floats, err := core.GetNumbersAsFloat(objects) if err != nil { return nil, err } @@ -1678,12 +1678,12 @@ type PdfColorspaceICCBased struct { Alternate PdfColorspace // Alternate colorspace for non-conforming readers. // If omitted ICC not supported: then use DeviceGray, // DeviceRGB or DeviceCMYK for N=1,3,4 respectively. - Range []float64 // Array of 2xN numbers, specifying range of each color component. - Metadata *PdfObjectStream // Metadata stream. - Data []byte // ICC colormap data. + Range []float64 // Array of 2xN numbers, specifying range of each color component. + Metadata *core.PdfObjectStream // Metadata stream. + Data []byte // ICC colormap data. - container *PdfIndirectObject - stream *PdfObjectStream + container *core.PdfIndirectObject + stream *core.PdfObjectStream } func (cs *PdfColorspaceICCBased) GetNumComponents() int { @@ -1712,14 +1712,14 @@ func NewPdfColorspaceICCBased(N int) (*PdfColorspaceICCBased, error) { } // Input format [/ICCBased stream] -func newPdfColorspaceICCBasedFromPdfObject(obj PdfObject) (*PdfColorspaceICCBased, error) { +func newPdfColorspaceICCBasedFromPdfObject(obj core.PdfObject) (*PdfColorspaceICCBased, error) { cs := &PdfColorspaceICCBased{} - if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect { + if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { cs.container = indObj } - obj = TraceToDirectObject(obj) - array, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + array, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("type error") } @@ -1729,8 +1729,8 @@ func newPdfColorspaceICCBasedFromPdfObject(obj PdfObject) (*PdfColorspaceICCBase } // Name. - obj = TraceToDirectObject(array.Get(0)) - name, ok := obj.(*PdfObjectName) + obj = core.TraceToDirectObject(array.Get(0)) + name, ok := obj.(*core.PdfObjectName) if !ok { return nil, fmt.Errorf("ICCBased name not a Name object") } @@ -1740,7 +1740,7 @@ func newPdfColorspaceICCBasedFromPdfObject(obj PdfObject) (*PdfColorspaceICCBase // Stream obj = array.Get(1) - stream, ok := obj.(*PdfObjectStream) + stream, ok := obj.(*core.PdfObjectStream) if !ok { common.Log.Error("ICCBased not pointing to stream: %T", obj) return nil, fmt.Errorf("ICCBased stream invalid") @@ -1748,7 +1748,7 @@ func newPdfColorspaceICCBasedFromPdfObject(obj PdfObject) (*PdfColorspaceICCBase dict := stream.PdfObjectDictionary - n, ok := dict.Get("N").(*PdfObjectInteger) + n, ok := dict.Get("N").(*core.PdfObjectInteger) if !ok { return nil, fmt.Errorf("ICCBased missing N from stream dict") } @@ -1766,8 +1766,8 @@ func newPdfColorspaceICCBasedFromPdfObject(obj PdfObject) (*PdfColorspaceICCBase } if obj := dict.Get("Range"); obj != nil { - obj = TraceToDirectObject(obj) - array, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + array, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("ICCBased Range not an array") } @@ -1782,14 +1782,14 @@ func newPdfColorspaceICCBasedFromPdfObject(obj PdfObject) (*PdfColorspaceICCBase } if obj := dict.Get("Metadata"); obj != nil { - stream, ok := obj.(*PdfObjectStream) + stream, ok := obj.(*core.PdfObjectStream) if !ok { return nil, fmt.Errorf("ICCBased Metadata not a stream") } cs.Metadata = stream } - data, err := DecodeStream(stream) + data, err := core.DecodeStream(stream) if err != nil { return nil, err } @@ -1800,20 +1800,20 @@ func newPdfColorspaceICCBasedFromPdfObject(obj PdfObject) (*PdfColorspaceICCBase } // ToPdfObject returns colorspace in a PDF object format [name stream] -func (cs *PdfColorspaceICCBased) ToPdfObject() PdfObject { - csObj := &PdfObjectArray{} +func (cs *PdfColorspaceICCBased) ToPdfObject() core.PdfObject { + csObj := &core.PdfObjectArray{} - csObj.Append(MakeName("ICCBased")) + csObj.Append(core.MakeName("ICCBased")) - var stream *PdfObjectStream + var stream *core.PdfObjectStream if cs.stream != nil { stream = cs.stream } else { - stream = &PdfObjectStream{} + stream = &core.PdfObjectStream{} } - dict := MakeDict() + dict := core.MakeDict() - dict.Set("N", MakeInteger(int64(cs.N))) + dict.Set("N", core.MakeInteger(int64(cs.N))) if cs.Alternate != nil { dict.Set("Alternate", cs.Alternate.ToPdfObject()) @@ -1823,15 +1823,15 @@ func (cs *PdfColorspaceICCBased) ToPdfObject() PdfObject { dict.Set("Metadata", cs.Metadata) } if cs.Range != nil { - var ranges []PdfObject + var ranges []core.PdfObject for _, r := range cs.Range { - ranges = append(ranges, MakeFloat(r)) + ranges = append(ranges, core.MakeFloat(r)) } - dict.Set("Range", MakeArray(ranges...)) + dict.Set("Range", core.MakeArray(ranges...)) } // Encode with a default encoder? - dict.Set("Length", MakeInteger(int64(len(cs.Data)))) + dict.Set("Length", core.MakeInteger(int64(len(cs.Data)))) // Need to have a representation of the stream... stream.Stream = cs.Data stream.PdfObjectDictionary = dict @@ -1865,7 +1865,7 @@ func (cs *PdfColorspaceICCBased) ColorFromFloats(vals []float64) (PdfColor, erro return cs.Alternate.ColorFromFloats(vals) } -func (cs *PdfColorspaceICCBased) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceICCBased) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if cs.Alternate == nil { if cs.N == 1 { cs := NewPdfColorspaceDeviceGray() @@ -1949,8 +1949,8 @@ func (cs *PdfColorspaceICCBased) ImageToRGB(img Image) (Image, error) { // Pattern color. type PdfColorPattern struct { - Color PdfColor // Color defined in underlying colorspace. - PatternName PdfObjectName // Name of the pattern (reference via resource dicts). + Color PdfColor // Color defined in underlying colorspace. + PatternName core.PdfObjectName // Name of the pattern (reference via resource dicts). } // PdfColorspaceSpecialPattern is a Pattern colorspace. @@ -1958,7 +1958,7 @@ type PdfColorPattern struct { type PdfColorspaceSpecialPattern struct { UnderlyingCS PdfColorspace - container *PdfIndirectObject + container *core.PdfIndirectObject } func NewPdfColorspaceSpecialPattern() *PdfColorspaceSpecialPattern { @@ -1978,16 +1978,16 @@ func (cs *PdfColorspaceSpecialPattern) DecodeArray() []float64 { return []float64{} } -func newPdfColorspaceSpecialPatternFromPdfObject(obj PdfObject) (*PdfColorspaceSpecialPattern, error) { +func newPdfColorspaceSpecialPatternFromPdfObject(obj core.PdfObject) (*PdfColorspaceSpecialPattern, error) { common.Log.Trace("New Pattern CS from obj: %s %T", obj.String(), obj) cs := NewPdfColorspaceSpecialPattern() - if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect { + if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { cs.container = indObj } - obj = TraceToDirectObject(obj) - if name, isName := obj.(*PdfObjectName); isName { + obj = core.TraceToDirectObject(obj) + if name, isName := obj.(*core.PdfObjectName); isName { if *name != "Pattern" { return nil, fmt.Errorf("invalid name") } @@ -1995,7 +1995,7 @@ func newPdfColorspaceSpecialPatternFromPdfObject(obj PdfObject) (*PdfColorspaceS return cs, nil } - array, ok := obj.(*PdfObjectArray) + array, ok := obj.(*core.PdfObjectArray) if !ok { common.Log.Error("Invalid Pattern CS Object: %#v", obj) return nil, fmt.Errorf("invalid Pattern CS object") @@ -2006,7 +2006,7 @@ func newPdfColorspaceSpecialPatternFromPdfObject(obj PdfObject) (*PdfColorspaceS } obj = array.Get(0) - if name, isName := obj.(*PdfObjectName); isName { + if name, isName := obj.(*core.PdfObjectName); isName { if *name != "Pattern" { common.Log.Error("Invalid Pattern CS array name: %#v", name) return nil, fmt.Errorf("invalid name") @@ -2016,7 +2016,7 @@ func newPdfColorspaceSpecialPatternFromPdfObject(obj PdfObject) (*PdfColorspaceS // Has an underlying color space. if array.Len() > 1 { obj = array.Get(1) - obj = TraceToDirectObject(obj) + obj = core.TraceToDirectObject(obj) baseCS, err := NewPdfColorspaceFromPdfObject(obj) if err != nil { return nil, err @@ -2028,12 +2028,12 @@ func newPdfColorspaceSpecialPatternFromPdfObject(obj PdfObject) (*PdfColorspaceS return cs, nil } -func (cs *PdfColorspaceSpecialPattern) ToPdfObject() PdfObject { +func (cs *PdfColorspaceSpecialPattern) ToPdfObject() core.PdfObject { if cs.UnderlyingCS == nil { - return MakeName("Pattern") + return core.MakeName("Pattern") } - csObj := MakeArray(MakeName("Pattern")) + csObj := core.MakeArray(core.MakeName("Pattern")) csObj.Append(cs.UnderlyingCS.ToPdfObject()) if cs.container != nil { @@ -2054,17 +2054,17 @@ func (cs *PdfColorspaceSpecialPattern) ColorFromFloats(vals []float64) (PdfColor // ColorFromPdfObjects loads the color from PDF objects. // The first objects (if present) represent the color in underlying colorspace. The last one represents // the name of the pattern. -func (cs *PdfColorspaceSpecialPattern) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceSpecialPattern) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) < 1 { return nil, errors.New("invalid number of parameters") } patternColor := &PdfColorPattern{} // Pattern name. - pname, ok := objects[len(objects)-1].(*PdfObjectName) + pname, ok := objects[len(objects)-1].(*core.PdfObjectName) if !ok { common.Log.Debug("Pattern name not a name (got %T)", objects[len(objects)-1]) - return nil, ErrTypeError + return nil, ErrTypeCheck } patternColor.PatternName = *pname @@ -2093,7 +2093,7 @@ func (cs *PdfColorspaceSpecialPattern) ColorToRGB(color PdfColor) (PdfColor, err patternColor, ok := color.(*PdfColorPattern) if !ok { common.Log.Debug("Color not pattern (got %T)", color) - return nil, ErrTypeError + return nil, ErrTypeCheck } if patternColor.Color == nil { @@ -2120,11 +2120,11 @@ func (cs *PdfColorspaceSpecialPattern) ImageToRGB(img Image) (Image, error) { type PdfColorspaceSpecialIndexed struct { Base PdfColorspace HiVal int - Lookup PdfObject + Lookup core.PdfObject colorLookup []byte // m*(hival+1); m is number of components in Base colorspace - container *PdfIndirectObject + container *core.PdfIndirectObject } func NewPdfColorspaceSpecialIndexed() *PdfColorspaceSpecialIndexed { @@ -2146,15 +2146,15 @@ func (cs *PdfColorspaceSpecialIndexed) DecodeArray() []float64 { return []float64{0, float64(cs.HiVal)} } -func newPdfColorspaceSpecialIndexedFromPdfObject(obj PdfObject) (*PdfColorspaceSpecialIndexed, error) { +func newPdfColorspaceSpecialIndexedFromPdfObject(obj core.PdfObject) (*PdfColorspaceSpecialIndexed, error) { cs := NewPdfColorspaceSpecialIndexed() - if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect { + if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { cs.container = indObj } - obj = TraceToDirectObject(obj) - array, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + array, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("type error") } @@ -2165,7 +2165,7 @@ func newPdfColorspaceSpecialIndexedFromPdfObject(obj PdfObject) (*PdfColorspaceS // Check name. obj = array.Get(0) - name, ok := obj.(*PdfObjectName) + name, ok := obj.(*core.PdfObjectName) if !ok { return nil, fmt.Errorf("indexed CS: invalid name") } @@ -2180,7 +2180,7 @@ func newPdfColorspaceSpecialIndexedFromPdfObject(obj PdfObject) (*PdfColorspaceS 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 + return nil, errRangeError } baseCs, err := NewPdfColorspaceFromPdfObject(obj) @@ -2191,7 +2191,7 @@ func newPdfColorspaceSpecialIndexedFromPdfObject(obj PdfObject) (*PdfColorspaceS // Get hi val. obj = array.Get(2) - val, err := GetNumberAsInt64(obj) + val, err := core.GetNumberAsInt64(obj) if err != nil { return nil, err } @@ -2203,15 +2203,15 @@ func newPdfColorspaceSpecialIndexedFromPdfObject(obj PdfObject) (*PdfColorspaceS // Index table. obj = array.Get(3) cs.Lookup = obj - obj = TraceToDirectObject(obj) + obj = core.TraceToDirectObject(obj) var data []byte - if str, ok := obj.(*PdfObjectString); ok { + if str, ok := obj.(*core.PdfObjectString); ok { data = str.Bytes() common.Log.Trace("Indexed string color data: % d", data) - } else if stream, ok := obj.(*PdfObjectStream); ok { + } else if stream, ok := obj.(*core.PdfObjectStream); ok { common.Log.Trace("Indexed stream: %s", obj.String()) common.Log.Trace("Encoded (%d) : %# x", len(stream.Stream), stream.Stream) - decoded, err := DecodeStream(stream) + decoded, err := core.DecodeStream(stream) if err != nil { return nil, err } @@ -2261,12 +2261,12 @@ func (cs *PdfColorspaceSpecialIndexed) ColorFromFloats(vals []float64) (PdfColor return color, nil } -func (cs *PdfColorspaceSpecialIndexed) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceSpecialIndexed) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != 1 { return nil, errors.New("range check") } - floats, err := GetNumbersAsFloat(objects) + floats, err := core.GetNumbersAsFloat(objects) if err != nil { return nil, err } @@ -2328,10 +2328,10 @@ func (cs *PdfColorspaceSpecialIndexed) ImageToRGB(img Image) (Image, error) { } // ToPdfObject converts colorspace to a PDF object. [/Indexed base hival lookup] -func (cs *PdfColorspaceSpecialIndexed) ToPdfObject() PdfObject { - csObj := MakeArray(MakeName("Indexed")) +func (cs *PdfColorspaceSpecialIndexed) ToPdfObject() core.PdfObject { + csObj := core.MakeArray(core.MakeName("Indexed")) csObj.Append(cs.Base.ToPdfObject()) - csObj.Append(MakeInteger(int64(cs.HiVal))) + csObj.Append(core.MakeInteger(int64(cs.HiVal))) csObj.Append(cs.Lookup) if cs.container != nil { @@ -2350,12 +2350,12 @@ func (cs *PdfColorspaceSpecialIndexed) ToPdfObject() PdfObject { // // Format: [/Separation name alternateSpace tintTransform] type PdfColorspaceSpecialSeparation struct { - ColorantName *PdfObjectName + ColorantName *core.PdfObjectName AlternateSpace PdfColorspace TintTransform PdfFunction // Container, if when parsing CS array is inside a container. - container *PdfIndirectObject + container *core.PdfIndirectObject } func NewPdfColorspaceSpecialSeparation() *PdfColorspaceSpecialSeparation { @@ -2377,18 +2377,18 @@ func (cs *PdfColorspaceSpecialSeparation) DecodeArray() []float64 { } // Object is an array or indirect object containing the array. -func newPdfColorspaceSpecialSeparationFromPdfObject(obj PdfObject) (*PdfColorspaceSpecialSeparation, error) { +func newPdfColorspaceSpecialSeparationFromPdfObject(obj core.PdfObject) (*PdfColorspaceSpecialSeparation, error) { cs := NewPdfColorspaceSpecialSeparation() - // If within an indirect object, then make a note of it. If we write out the PdfObject later + // If within an indirect object, then make a note of it. If we write out thecore.PdfObject later // we can reference the same container. Otherwise is not within a container, but rather // a new array. - if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect { + if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { cs.container = indObj } - obj = TraceToDirectObject(obj) - array, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + array, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("separation CS: Invalid object") } @@ -2399,7 +2399,7 @@ func newPdfColorspaceSpecialSeparationFromPdfObject(obj PdfObject) (*PdfColorspa // Check name. obj = array.Get(0) - name, ok := obj.(*PdfObjectName) + name, ok := obj.(*core.PdfObjectName) if !ok { return nil, fmt.Errorf("separation CS: invalid family name") } @@ -2409,7 +2409,7 @@ func newPdfColorspaceSpecialSeparationFromPdfObject(obj PdfObject) (*PdfColorspa // Get colorant name. obj = array.Get(1) - name, ok = obj.(*PdfObjectName) + name, ok = obj.(*core.PdfObjectName) if !ok { return nil, fmt.Errorf("separation CS: Invalid colorant name") } @@ -2434,8 +2434,8 @@ func newPdfColorspaceSpecialSeparationFromPdfObject(obj PdfObject) (*PdfColorspa return cs, nil } -func (cs *PdfColorspaceSpecialSeparation) ToPdfObject() PdfObject { - csArray := MakeArray(MakeName("Separation")) +func (cs *PdfColorspaceSpecialSeparation) ToPdfObject() core.PdfObject { + csArray := core.MakeArray(core.MakeName("Separation")) csArray.Append(cs.ColorantName) csArray.Append(cs.AlternateSpace.ToPdfObject()) @@ -2475,12 +2475,12 @@ func (cs *PdfColorspaceSpecialSeparation) ColorFromFloats(vals []float64) (PdfCo return color, nil } -func (cs *PdfColorspaceSpecialSeparation) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceSpecialSeparation) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != 1 { return nil, errors.New("range check") } - floats, err := GetNumbersAsFloat(objects) + floats, err := core.GetNumbersAsFloat(objects) if err != nil { return nil, err } @@ -2552,13 +2552,13 @@ func (cs *PdfColorspaceSpecialSeparation) ImageToRGB(img Image) (Image, error) { // Format: [/DeviceN names alternateSpace tintTransform] // or: [/DeviceN names alternateSpace tintTransform attributes] type PdfColorspaceDeviceN struct { - ColorantNames *PdfObjectArray + ColorantNames *core.PdfObjectArray AlternateSpace PdfColorspace TintTransform PdfFunction Attributes *PdfColorspaceDeviceNAttributes // Optional - container *PdfIndirectObject + container *core.PdfIndirectObject } // NewPdfColorspaceDeviceN returns an initialized PdfColorspaceDeviceN. @@ -2587,21 +2587,21 @@ func (cs *PdfColorspaceDeviceN) DecodeArray() []float64 { return decode } -// newPdfColorspaceDeviceNFromPdfObject loads a DeviceN colorspace from a PdfObjectArray which can be +// newPdfColorspaceDeviceNFromPdfObject loads a DeviceN colorspace from a core.PdfObjectArray which can be // contained within an indirect object. -func newPdfColorspaceDeviceNFromPdfObject(obj PdfObject) (*PdfColorspaceDeviceN, error) { +func newPdfColorspaceDeviceNFromPdfObject(obj core.PdfObject) (*PdfColorspaceDeviceN, error) { cs := NewPdfColorspaceDeviceN() - // If within an indirect object, then make a note of it. If we write out the PdfObject later + // If within an indirect object, then make a note of it. If we write out thecore.PdfObject later // we can reference the same container. Otherwise is not within a container, but rather // a new array. - if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect { + if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { cs.container = indObj } // Check the CS array. - obj = TraceToDirectObject(obj) - csArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + csArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("deviceN CS: Invalid object") } @@ -2612,7 +2612,7 @@ func newPdfColorspaceDeviceNFromPdfObject(obj PdfObject) (*PdfColorspaceDeviceN, // Check name. obj = csArray.Get(0) - name, ok := obj.(*PdfObjectName) + name, ok := obj.(*core.PdfObjectName) if !ok { return nil, fmt.Errorf("deviceN CS: invalid family name") } @@ -2622,8 +2622,8 @@ func newPdfColorspaceDeviceNFromPdfObject(obj PdfObject) (*PdfColorspaceDeviceN, // Get colorant names. Specifies the number of components too. obj = csArray.Get(1) - obj = TraceToDirectObject(obj) - nameArray, ok := obj.(*PdfObjectArray) + obj = core.TraceToDirectObject(obj) + nameArray, ok := obj.(*core.PdfObjectArray) if !ok { return nil, fmt.Errorf("deviceN CS: Invalid names array") } @@ -2656,11 +2656,11 @@ func newPdfColorspaceDeviceNFromPdfObject(obj PdfObject) (*PdfColorspaceDeviceN, return cs, nil } -// ToPdfObject returns a *PdfIndirectObject containing a *PdfObjectArray representation of the DeviceN colorspace. +// ToPdfObject returns a *core.PdfIndirectObject containing a *core.PdfObjectArray representation of the DeviceN colorspace. // Format: [/DeviceN names alternateSpace tintTransform] // or: [/DeviceN names alternateSpace tintTransform attributes] -func (cs *PdfColorspaceDeviceN) ToPdfObject() PdfObject { - csArray := MakeArray(MakeName("DeviceN")) +func (cs *PdfColorspaceDeviceN) ToPdfObject() core.PdfObject { + csArray := core.MakeArray(core.MakeName("DeviceN")) csArray.Append(cs.ColorantNames) csArray.Append(cs.AlternateSpace.ToPdfObject()) csArray.Append(cs.TintTransform.ToPdfObject()) @@ -2694,14 +2694,14 @@ func (cs *PdfColorspaceDeviceN) ColorFromFloats(vals []float64) (PdfColor, error return color, nil } -// ColorFromPdfObjects returns a new PdfColor based on input color components. The input PdfObjects should +// ColorFromPdfObjects returns a new PdfColor based on input color components. The inputcore.PdfObjects should // be numeric. -func (cs *PdfColorspaceDeviceN) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) { +func (cs *PdfColorspaceDeviceN) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != cs.GetNumComponents() { return nil, errors.New("range check") } - floats, err := GetNumbersAsFloat(objects) + floats, err := core.GetNumbersAsFloat(objects) if err != nil { return nil, err } @@ -2761,30 +2761,30 @@ func (cs *PdfColorspaceDeviceN) ImageToRGB(img Image) (Image, error) { // and may instead use a custom blending algorithms, along with other information provided in the attributes // dictionary if present. type PdfColorspaceDeviceNAttributes struct { - Subtype *PdfObjectName // DeviceN or NChannel (DeviceN default) - Colorants PdfObject - Process PdfObject - MixingHints PdfObject + Subtype *core.PdfObjectName // DeviceN or NChannel (DeviceN default) + Colorants core.PdfObject + Process core.PdfObject + MixingHints core.PdfObject // Optional - container *PdfIndirectObject + container *core.PdfIndirectObject } // newPdfColorspaceDeviceNAttributesFromPdfObject loads a PdfColorspaceDeviceNAttributes from an input -// PdfObjectDictionary (direct/indirect). -func newPdfColorspaceDeviceNAttributesFromPdfObject(obj PdfObject) (*PdfColorspaceDeviceNAttributes, error) { +//core.PdfObjectDictionary (direct/indirect). +func newPdfColorspaceDeviceNAttributesFromPdfObject(obj core.PdfObject) (*PdfColorspaceDeviceNAttributes, error) { attr := &PdfColorspaceDeviceNAttributes{} - var dict *PdfObjectDictionary - if indObj, isInd := obj.(*PdfIndirectObject); isInd { + var dict *core.PdfObjectDictionary + if indObj, isInd := obj.(*core.PdfIndirectObject); isInd { attr.container = indObj var ok bool - dict, ok = indObj.PdfObject.(*PdfObjectDictionary) + dict, ok = indObj.PdfObject.(*core.PdfObjectDictionary) if !ok { common.Log.Error("DeviceN attribute type error") return nil, errors.New("type error") } - } else if d, isDict := obj.(*PdfObjectDictionary); isDict { + } else if d, isDict := obj.(*core.PdfObjectDictionary); isDict { dict = d } else { common.Log.Error("DeviceN attribute type error") @@ -2792,7 +2792,7 @@ func newPdfColorspaceDeviceNAttributesFromPdfObject(obj PdfObject) (*PdfColorspa } if obj := dict.Get("Subtype"); obj != nil { - name, ok := TraceToDirectObject(obj).(*PdfObjectName) + name, ok := core.TraceToDirectObject(obj).(*core.PdfObjectName) if !ok { common.Log.Error("DeviceN attribute Subtype type error") return nil, errors.New("type error") @@ -2818,8 +2818,8 @@ func newPdfColorspaceDeviceNAttributesFromPdfObject(obj PdfObject) (*PdfColorspa // ToPdfObject returns a PdfObject representation of PdfColorspaceDeviceNAttributes as a PdfObjectDictionary directly // or indirectly within an indirect object container. -func (cs *PdfColorspaceDeviceNAttributes) ToPdfObject() PdfObject { - dict := MakeDict() +func (cs *PdfColorspaceDeviceNAttributes) ToPdfObject() core.PdfObject { + dict := core.MakeDict() if cs.Subtype != nil { dict.Set("Subtype", cs.Subtype) diff --git a/pdf/model/const.go b/pdf/model/const.go index af83e992..81379645 100644 --- a/pdf/model/const.go +++ b/pdf/model/const.go @@ -10,15 +10,16 @@ import ( ) // Errors when parsing/loading data in PDF. +// TODO(gunnsth): Unexport errors. var ( ErrRequiredAttributeMissing = errors.New("required attribute missing") ErrInvalidAttribute = errors.New("invalid attribute") ErrTypeCheck = errors.New("type check") - //ErrRangeError = errors.New("range check error") - ErrEncrypted = errors.New("file needs to be decrypted first") - ErrNoFont = errors.New("font not defined") - ErrFontNotSupported = errors.New("unsupported font") - ErrType1CFontNotSupported = errors.New("Type1C fonts are not currently supported") - ErrType3FontNotSupported = errors.New("Type3 fonts are not currently supported") - ErrTTCmapNotSupported = errors.New("unsupported TrueType cmap format") + errRangeError = errors.New("range check error") + ErrEncrypted = errors.New("file needs to be decrypted first") + ErrNoFont = errors.New("font not defined") + ErrFontNotSupported = errors.New("unsupported font") + ErrType1CFontNotSupported = errors.New("Type1C fonts are not currently supported") + ErrType3FontNotSupported = errors.New("Type3 fonts are not currently supported") + ErrTTCmapNotSupported = errors.New("unsupported TrueType cmap format") ) From 884516480e880b9ffa29aafaffd12ff8b91b9be7 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Fri, 11 Jan 2019 09:51:10 +0000 Subject: [PATCH 22/24] Clean up --- pdf/model/colorspace.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pdf/model/colorspace.go b/pdf/model/colorspace.go index d7867797..5be7e39d 100644 --- a/pdf/model/colorspace.go +++ b/pdf/model/colorspace.go @@ -2587,7 +2587,7 @@ func (cs *PdfColorspaceDeviceN) DecodeArray() []float64 { return decode } -// newPdfColorspaceDeviceNFromPdfObject loads a DeviceN colorspace from a core.PdfObjectArray which can be +// newPdfColorspaceDeviceNFromPdfObject loads a DeviceN colorspace from a PdfObjectArray which can be // contained within an indirect object. func newPdfColorspaceDeviceNFromPdfObject(obj core.PdfObject) (*PdfColorspaceDeviceN, error) { cs := NewPdfColorspaceDeviceN() @@ -2656,7 +2656,7 @@ func newPdfColorspaceDeviceNFromPdfObject(obj core.PdfObject) (*PdfColorspaceDev return cs, nil } -// ToPdfObject returns a *core.PdfIndirectObject containing a *core.PdfObjectArray representation of the DeviceN colorspace. +// ToPdfObject returns a *PdfIndirectObject containing a *PdfObjectArray representation of the DeviceN colorspace. // Format: [/DeviceN names alternateSpace tintTransform] // or: [/DeviceN names alternateSpace tintTransform attributes] func (cs *PdfColorspaceDeviceN) ToPdfObject() core.PdfObject { @@ -2694,7 +2694,7 @@ func (cs *PdfColorspaceDeviceN) ColorFromFloats(vals []float64) (PdfColor, error return color, nil } -// ColorFromPdfObjects returns a new PdfColor based on input color components. The inputcore.PdfObjects should +// ColorFromPdfObjects returns a new PdfColor based on input color components. The input PdfObjects should // be numeric. func (cs *PdfColorspaceDeviceN) ColorFromPdfObjects(objects []core.PdfObject) (PdfColor, error) { if len(objects) != cs.GetNumComponents() { @@ -2771,7 +2771,7 @@ type PdfColorspaceDeviceNAttributes struct { } // newPdfColorspaceDeviceNAttributesFromPdfObject loads a PdfColorspaceDeviceNAttributes from an input -//core.PdfObjectDictionary (direct/indirect). +// PdfObjectDictionary (direct/indirect). func newPdfColorspaceDeviceNAttributesFromPdfObject(obj core.PdfObject) (*PdfColorspaceDeviceNAttributes, error) { attr := &PdfColorspaceDeviceNAttributes{} From 28e1bd14dd2c184dfede370714dee5eac9d775a3 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Fri, 11 Jan 2019 09:54:40 +0000 Subject: [PATCH 23/24] Clean up --- pdf/model/colorspace.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pdf/model/colorspace.go b/pdf/model/colorspace.go index 5be7e39d..a82006fe 100644 --- a/pdf/model/colorspace.go +++ b/pdf/model/colorspace.go @@ -2380,7 +2380,7 @@ func (cs *PdfColorspaceSpecialSeparation) DecodeArray() []float64 { func newPdfColorspaceSpecialSeparationFromPdfObject(obj core.PdfObject) (*PdfColorspaceSpecialSeparation, error) { cs := NewPdfColorspaceSpecialSeparation() - // If within an indirect object, then make a note of it. If we write out thecore.PdfObject later + // If within an indirect object, then make a note of it. If we write out the PdfObject later // we can reference the same container. Otherwise is not within a container, but rather // a new array. if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { @@ -2592,7 +2592,7 @@ func (cs *PdfColorspaceDeviceN) DecodeArray() []float64 { func newPdfColorspaceDeviceNFromPdfObject(obj core.PdfObject) (*PdfColorspaceDeviceN, error) { cs := NewPdfColorspaceDeviceN() - // If within an indirect object, then make a note of it. If we write out thecore.PdfObject later + // If within an indirect object, then make a note of it. If we write out the PdfObject later // we can reference the same container. Otherwise is not within a container, but rather // a new array. if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect { From e5f921d1ea06d65cb4531e37dbadadd7ec9f3a92 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Fri, 11 Jan 2019 10:45:08 +0000 Subject: [PATCH 24/24] Add codecov.yaml Disable patch and changes status fails. Set baseline for project test coverage. --- codecov.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..740a2b90 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,8 @@ +coverage: + status: + project: + default: + target: 35% + threshold: 1% + patch: false + changes: false \ No newline at end of file