* 'master' of https://github.com/peterwilliams97/unidoc: (50 commits)
  Fixing lab colorspace component input ranges.  Fix Indexed cs Image to rgb conversion.
  Make float parsing more like gs
  Fixed Lab bounds
  Added dummy encodings
  Added dummy encodings
  Fix PS processing of dup operand. Fixes #98.
  Check sizes for memory allocation based on pdf user inputs. Fixes #107.
  Check to avoid division by zero.  Fixes #106.
  Add GetObjectNums
  Address go vet issues
  Fix comment typo
  Fixed some bugs found while getting pdf_descibe.go to work
  Address golint recommendations in core
  Address core golint recommendations in crypt, io
  Add check for base colorspace type when loading Indexed colorspace.  Fixes #95.
  Address more golint recommendations  #89
  Checks on stated byte lengths in xref stream objects.  Closes #94.
  Address golint recommendations. Add TODO comments for recommended future refactoring work in next major release.
  Only attempt to load annotation from a valid indirect object for annotation Popup entries.  Fixes #91.
  Address godoc code block line wrapping
  ...
This commit is contained in:
Peter Williams 2018-01-11 17:43:17 +11:00
commit e4adfd91a3
77 changed files with 2584 additions and 1579 deletions

46
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at support@unidoc.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -1,48 +1,38 @@
# Version 2
The version 2 of UniDoc is currently in alpha.
The pdf functionality has been split into modules. The core subpackage contains core PDF file parsing functionality and primitive objects, whereas the model subpackage provides a higher level interface to the PDF.
The creator package provides a convenient interface for creating image and text based PDF files and reports.
# Migrating from version 1.
Migrating is fairly straightforward. A few things are incompatible and will be listed here prior to release.
---
# UniDoc
[UniDoc](http://unidoc.io) is a fast and powerful open source library for document manipulation starting off as a PDF
toolkit. This is a library written and supported by the owners
of the [FoxyUtils.com](https://foxyutils.com) website.
This library is used to power many of the PDF services offered by [FoxyUtils](https://foxyutils.com). The goal is to extend it to
eventually support all of the offered services.
[UniDoc](http://unidoc.io) is a fast and powerful open source PDF library for Go (golang). The library is written and supported by the owners of the [FoxyUtils.com](https://foxyutils.com) website, where the library is used to power many of the PDF services offered.
[![wercker status](https://app.wercker.com/status/22b50db125a6d376080f3f0c80d085fa/s/master "wercker status")](https://app.wercker.com/project/bykey/22b50db125a6d376080f3f0c80d085fa)
[![GoDoc](https://godoc.org/github.com/unidoc/unidoc?status.svg)](https://godoc.org/github.com/unidoc/unidoc)
# Version 2
Version 2.0.0 has been released. Version 2 represents a major improvement over version 1 with capabilities for modifying
and generating PDF contents. The library has been split up into three major packages and a
few smaller ones. The **core** package contains core PDF file parsing functionality and
primitive objects, whereas the **model** subpackage provides a higher level interface to the PDF.
The **creator** package provides a convenient interface for creating image and text based PDF files
and reports.
See the release announcement: [https://unidoc.io/news/unidoc-v2-released](https://unidoc.io/news/unidoc-v2-released)
## Installation
~~~
go get github.com/unidoc/unidoc
go get github.com/unidoc/unidoc/...
~~~
## Vendoring
For reliability, we recommend using specific versions and the vendoring capability of golang.
Check out the Releases section to see the tagged releases.
## Overview
* Many [features](http://unidoc.io/features) with documented examples.
* Self contained with no external dependencies
* Developer friendly
## Examples
Multiple examples are provided in our example repository.
See the [unidoc-examples](https://github.com/unidoc/unidoc-examples/tree/master) folder.
Many features for processing PDF files with [documented examples](https://unidoc.io/examples) on our website.
Contact us if you need any specific examples.
## Vendoring
For reliability, we recommend using specific versions and the vendoring capability of golang.
Check out the Releases section to see the tagged releases.
## Copying/License
UniDoc is licensed as [AGPL][agpl] software (with extra terms as specified in our license).
@ -60,16 +50,21 @@ These activities include:
* creating/manipulating documents for users in a web/server/cloud application
* shipping UniDoc with a closed source product
Contact sales for more info: sales@unidoc.io.
Please see [pricing](http://unidoc.io/pricing) to purchase a commercial license or contact sales at sales@unidoc.io for more info.
## Contributing
Contributors need to approve the [Contributor License Agreement](https://docs.google.com/a/owlglobal.io/forms/d/1PfTjEAi67-x0JOTU45SDonJnWy1fWB_J1aopGss34bY/viewform) before any code will be reviewed. Preferably add a test case to make sure there is no regression and that the new behaviour is as expected.
## Support
## Support and consulting
Please email us at support@unidoc.io for any queries.
Technical support is included with a purchase of a license, as listed on our [pricing](http://unidoc.io/pricing) page.
If you have any specific tasks that need to be done, we offer consulting in certain cases.
Please contact us with a brief summary of what you need and we will get back to you with a quote, if appropriate.
## Stay up to date
* Follow us on [twitter](https://twitter.com/unidoclib)

View File

@ -12,11 +12,11 @@ import (
const releaseYear = 2017
const releaseMonth = 7
const releaseDay = 17
const releaseHour = 20
const releaseMin = 45
const releaseDay = 25
const releaseHour = 14
const releaseMin = 37
// Holds version information, when bumping this make sure to bump the released at stamp also.
const Version = "2.0.0-rc.1"
const Version = "2.0.0"
var ReleasedAt = time.Date(releaseYear, releaseMonth, releaseDay, releaseHour, releaseMin, 0, 0, time.UTC)

40
doc.go
View File

@ -3,14 +3,40 @@
* file 'LICENSE.md', which is part of this source code package.
*/
// UniDoc is a comprehensive PDF library for Go (golang). The library has advanced capabilities for generating,
// processing and modifying PDFs. UniDoc is written and supported by the owners of the
// FoxyUtils.com website, where the library is used to power many of the PDF services offered.
//
// UniDoc is a fast and powerful package for document manipulation starting
// off as a PDF toolkit. This is a commercial open source library written
// and supported by the owners of the [FoxyUtils.com](https://foxyutils.com)
// website.
// Getting More Information
//
// Please see the [examples](https://github.com/unidoc/unidoc-examples/tree/master)
// folder.
// Check out the Getting Started and Example sections, which showcase how to install UniDoc and provide numerous
// examples of using UniDoc to generate, process or modify PDF files.
// https://unidoc.io/examples/getting_started/
//
// The godoc for unidoc provides a detailed breakdown of the API and documentation for packages, types and methods.
// https://godoc.org/github.com/unidoc/unidoc
//
// Overview of Major Packages
//
// The API is composed of a few major packages:
//
// - common: Provides common shared types such as Logger and utilities to check
// license validity.
//
// - pdf/core: The core package defines the primitive PDF object types and handles
// the file reading I/O and parsing the primitive objects.
//
// - pdf/model: The model package builds on the core package, to represent the PDF as
// a structured model of the PDF primitive types. It has a reader and a writer to
// read and process a PDF file based on the structured model. This serves as a basis
// to perform a number of numerous tasks and can be used to work with a PDF in a
// medium to high level interface, although it does require an understanding of the
// PDF format and structure.
//
// - pdf/creator: The PDF creator makes it easy to create new PDFs or modify existing
// PDFs. It can also enable loading a template PDF, adding text/images and
// generating an output PDF. It can be used to add text, images, and generate text
// and graphical reports. It is designed with simplicity in mind, with the goal of
// making it easy to create reports without needing any knowledge about the PDF
// format or specifications.
package unidoc

View File

@ -1,3 +0,0 @@
package annotator

View File

@ -54,7 +54,7 @@ func (this *ContentStreamOperations) WrapIfNeeded() *ContentStreamOperations {
return this
}
*this = append([]*ContentStreamOperation{&ContentStreamOperation{Operand: "q"}}, *this...)
*this = append([]*ContentStreamOperation{{Operand: "q"}}, *this...)
depth := 0
for _, op := range *this {

View File

@ -64,6 +64,8 @@ func newEncoderFromInlineImage(inlineImage *ContentStreamInlineImage) (StreamEnc
return newFlateEncoderFromInlineImage(inlineImage, nil)
} else if *filterName == "LZW" {
return newLZWEncoderFromInlineImage(inlineImage, nil)
} else if *filterName == "CCF" {
return NewCCITTFaxEncoder(), nil
} else {
common.Log.Debug("Unsupported inline image encoding filter name : %s", *filterName)
return nil, errors.New("Unsupported inline encoding method")

View File

@ -163,11 +163,11 @@ func (this *ContentStreamInlineImage) GetColorSpace(resources *PdfPageResources)
return nil, errors.New("Invalid type")
}
if *name == "G" {
if *name == "G" || *name == "DeviceGray" {
return NewPdfColorspaceDeviceGray(), nil
} else if *name == "RGB" {
} else if *name == "RGB" || *name == "DeviceRGB" {
return NewPdfColorspaceDeviceRGB(), nil
} else if *name == "CMYK" {
} else if *name == "CMYK" || *name == "DeviceCMYK" {
return NewPdfColorspaceDeviceCMYK(), nil
} else if *name == "I" {
return nil, errors.New("Unsupported Index colorspace")

View File

@ -46,6 +46,7 @@ func (this *ContentStreamParser) Parse() (*ContentStreamOperations, error) {
obj, err, isOperand := this.parseObject()
if err != nil {
if err == io.EOF {
// End of data. Successful exit point.
return &operations, nil
}
return &operations, err
@ -69,9 +70,6 @@ func (this *ContentStreamParser) Parse() (*ContentStreamOperations, error) {
operation.Params = append(operation.Params, im)
}
}
common.Log.Debug("Operation list: %v\n", operations)
return &operations, nil
}
// Skip over any spaces. Returns the number of spaces skipped and
@ -189,7 +187,7 @@ func (this *ContentStreamParser) parseName() (PdfObjectName, error) {
// A conforming writer shall not use the PostScript syntax for numbers
// with non-decimal radices (such as 16#FFFE) or in exponential format
// (such as 6.02E23).
// Nontheless, we sometimes get numbers with exponential format, so
// Nonetheless, we sometimes get numbers with exponential format, so
// we will support it in the reader (no confusion with other types, so
// no compromise).
func (this *ContentStreamParser) parseNumber() (PdfObject, error) {
@ -234,6 +232,11 @@ func (this *ContentStreamParser) parseNumber() (PdfObject, error) {
if isFloat {
fVal, err := strconv.ParseFloat(numStr, 64)
if err != nil {
common.Log.Debug("Error parsing number %q err=%v. Using 0.0. Output may be incorrect", numStr, err)
fVal = 0.0
err = nil
}
o := PdfObjectFloat(fVal)
return &o, err
} else {

View File

@ -7,7 +7,6 @@ package contentstream
import (
"errors"
"fmt"
"github.com/unidoc/unidoc/common"
. "github.com/unidoc/unidoc/pdf/core"
@ -25,10 +24,6 @@ type GraphicsState struct {
type GraphicStateStack []GraphicsState
func (gs *GraphicsState) String() string {
return fmt.Sprintf("%+v", gs)
}
func (gsStack *GraphicStateStack) Push(gs GraphicsState) {
*gsStack = append(*gsStack, gs)
}
@ -127,7 +122,7 @@ func (csp *ContentStreamProcessor) getColorspace(name string, resources *PdfPage
}
// Otherwise unsupported.
common.Log.Error("Unknown colorspace requested: %s", name)
common.Log.Debug("Unknown colorspace requested: %s", name)
return nil, errors.New("Unsupported colorspace")
}
@ -194,7 +189,7 @@ func (csp *ContentStreamProcessor) getInitialColor(cs PdfColorspace) (PdfColor,
return nil, nil
}
common.Log.Error("Unable to determine initial color for unknown colorspace: %T", cs)
common.Log.Debug("Unable to determine initial color for unknown colorspace: %T", cs)
return nil, errors.New("Unsupported colorspace")
}
@ -207,6 +202,8 @@ func (this *ContentStreamProcessor) Process(resources *PdfPageResources) error {
this.graphicsState.ColorNonStroking = NewPdfColorDeviceGray(0)
for _, op := range this.operations {
var err error
// Internal handling.
switch op.Operand {
case "q":
@ -216,65 +213,34 @@ func (this *ContentStreamProcessor) Process(resources *PdfPageResources) error {
// Color operations (Table 74 p. 179)
case "CS":
err := this.handleCommand_CS(op, resources)
if err != nil {
return err
}
err = this.handleCommand_CS(op, resources)
case "cs":
err := this.handleCommand_cs(op, resources)
if err != nil {
return err
}
err = this.handleCommand_cs(op, resources)
case "SC":
err := this.handleCommand_SC(op, resources)
if err != nil {
return err
}
err = this.handleCommand_SC(op, resources)
case "SCN":
err := this.handleCommand_SCN(op, resources)
if err != nil {
return err
}
err = this.handleCommand_SCN(op, resources)
case "sc":
err := this.handleCommand_sc(op, resources)
if err != nil {
return err
}
err = this.handleCommand_sc(op, resources)
case "scn":
err := this.handleCommand_scn(op, resources)
if err != nil {
return err
}
err = this.handleCommand_scn(op, resources)
case "G":
err := this.handleCommand_G(op, resources)
if err != nil {
return err
}
err = this.handleCommand_G(op, resources)
case "g":
err := this.handleCommand_g(op, resources)
if err != nil {
return err
}
err = this.handleCommand_g(op, resources)
case "RG":
err := this.handleCommand_RG(op, resources)
if err != nil {
return err
}
err = this.handleCommand_RG(op, resources)
case "rg":
err := this.handleCommand_rg(op, resources)
if err != nil {
return err
}
err = this.handleCommand_rg(op, resources)
case "K":
err := this.handleCommand_K(op, resources)
if err != nil {
return err
}
err = this.handleCommand_K(op, resources)
case "k":
err := this.handleCommand_k(op, resources)
if err != nil {
return err
}
err = this.handleCommand_k(op, resources)
}
if err != nil {
common.Log.Debug("Processor handling error (%s): %v", op.Operand, err)
common.Log.Debug("Operand: %#v", op.Operand)
return err
}
// Check if have external handler also, and process if so.
@ -368,8 +334,8 @@ func (this *ContentStreamProcessor) handleCommand_SC(op *ContentStreamOperation,
cs := this.graphicsState.ColorspaceStroking
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}
@ -393,8 +359,8 @@ func (this *ContentStreamProcessor) handleCommand_SCN(op *ContentStreamOperation
if !isPatternCS(cs) {
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}
}
@ -415,8 +381,8 @@ func (this *ContentStreamProcessor) handleCommand_sc(op *ContentStreamOperation,
if !isPatternCS(cs) {
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}
}
@ -437,14 +403,15 @@ func (this *ContentStreamProcessor) handleCommand_scn(op *ContentStreamOperation
if !isPatternCS(cs) {
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}
}
color, err := cs.ColorFromPdfObjects(op.Params)
if err != nil {
common.Log.Debug("ERROR: Fail to get color from params: %+v (CS is %+v)", op.Params, cs)
return err
}
@ -458,8 +425,8 @@ func (this *ContentStreamProcessor) handleCommand_scn(op *ContentStreamOperation
func (this *ContentStreamProcessor) handleCommand_G(op *ContentStreamOperation, resources *PdfPageResources) error {
cs := NewPdfColorspaceDeviceGray()
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}
@ -479,8 +446,8 @@ func (this *ContentStreamProcessor) handleCommand_G(op *ContentStreamOperation,
func (this *ContentStreamProcessor) handleCommand_g(op *ContentStreamOperation, resources *PdfPageResources) error {
cs := NewPdfColorspaceDeviceGray()
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}
@ -500,8 +467,8 @@ func (this *ContentStreamProcessor) handleCommand_g(op *ContentStreamOperation,
func (this *ContentStreamProcessor) handleCommand_RG(op *ContentStreamOperation, resources *PdfPageResources) error {
cs := NewPdfColorspaceDeviceRGB()
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}
@ -520,8 +487,8 @@ func (this *ContentStreamProcessor) handleCommand_RG(op *ContentStreamOperation,
func (this *ContentStreamProcessor) handleCommand_rg(op *ContentStreamOperation, resources *PdfPageResources) error {
cs := NewPdfColorspaceDeviceRGB()
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}
@ -541,8 +508,8 @@ func (this *ContentStreamProcessor) handleCommand_rg(op *ContentStreamOperation,
func (this *ContentStreamProcessor) handleCommand_K(op *ContentStreamOperation, resources *PdfPageResources) error {
cs := NewPdfColorspaceDeviceCMYK()
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}
@ -561,8 +528,8 @@ func (this *ContentStreamProcessor) handleCommand_K(op *ContentStreamOperation,
func (this *ContentStreamProcessor) handleCommand_k(op *ContentStreamOperation, resources *PdfPageResources) error {
cs := NewPdfColorspaceDeviceCMYK()
if len(op.Params) != cs.GetNumComponents() {
common.Log.Error("Invalid number of parameters for SC")
common.Log.Error("Number %d not matching colorspace %T", len(op.Params), cs)
common.Log.Debug("Invalid number of parameters for SC")
common.Log.Debug("Number %d not matching colorspace %T", len(op.Params), cs)
return errors.New("Invalid number of parameters")
}

View File

@ -8,5 +8,11 @@ package core
import "errors"
var (
// ErrUnsupportedEncodingParameters error indicates that encoding/decoding was attempted with unsupported
// encoding parameters.
// For example when trying to encode with an unsupported Predictor (flate).
ErrUnsupportedEncodingParameters = errors.New("Unsupported encoding parameters")
ErrNoCCITTFaxDecode = errors.New("CCITTFaxDecode encoding is not yet implemented")
ErrNoJBIG2Decode = errors.New("JBIG2Decode encoding is not yet implemented")
ErrNoJPXDecode = errors.New("JPXDecode encoding is not yet implemented")
)

View File

@ -15,15 +15,20 @@ import (
"github.com/unidoc/unidoc/common"
)
// TODO (v3): Create a new type xrefType which can be an integer and can be used for improved type checking.
// TODO (v3): Unexport these constants and rename with camelCase.
const (
XREF_TABLE_ENTRY = iota
// XREF_TABLE_ENTRY indicates a normal xref table entry.
XREF_TABLE_ENTRY = iota
// XREF_OBJECT_STREAM indicates an xref entry in an xref object stream.
XREF_OBJECT_STREAM = iota
)
// Either can be in a normal xref table, or in an xref stream.
// Can point either to a file offset, or to an object stream.
// XrefFileOffset or XrefObjectStream...
// XrefObject defines a cross reference entry which is a map between object number (with generation number) and the
// location of the actual object, either as a file offset (xref table entry), or as a location within an xref
// stream object (xref object stream).
// TODO (v3): Unexport.
type XrefObject struct {
xtype int
objectNumber int
@ -35,27 +40,38 @@ type XrefObject struct {
osObjIndex int
}
// XrefTable is a map between object number and corresponding XrefObject.
// TODO (v3): Unexport.
// TODO: Consider changing to a slice, so can maintain the object order without sorting when analyzing.
type XrefTable map[int]XrefObject
// ObjectStream represents an object stream's information which can contain multiple indirect objects.
// The information specifies the number of objects and has information about offset locations for
// each object.
// TODO (v3): Unexport.
type ObjectStream struct {
N int
N int // TODO (v3): Unexport.
ds []byte
offsets map[int]int64
}
// ObjectStreams defines a map between object numbers (object streams only) and underlying ObjectStream information.
type ObjectStreams map[int]ObjectStream
// ObjectCache defines a map between object numbers and corresponding PdfObject. Serves as a cache for PdfObjects that
// have already been parsed.
// TODO (v3): Unexport.
type ObjectCache map[int]PdfObject
// Get an object from an object stream.
func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject, error) {
func (parser *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject, error) {
var bufReader *bytes.Reader
var objstm ObjectStream
var cached bool
objstm, cached = this.objstms[sobjNumber]
objstm, cached = parser.objstms[sobjNumber]
if !cached {
soi, err := this.LookupByNumber(sobjNumber)
soi, err := parser.LookupByNumber(sobjNumber)
if err != nil {
common.Log.Debug("Missing object stream with number %d", sobjNumber)
return nil, err
@ -66,19 +82,19 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject,
return nil, errors.New("Invalid object stream")
}
if this.crypter != nil && !this.crypter.isDecrypted(so) {
return nil, errors.New("Need to decrypt the stream !")
if parser.crypter != nil && !parser.crypter.isDecrypted(so) {
return nil, errors.New("Need to decrypt the stream")
}
sod := so.PdfObjectDictionary
common.Log.Trace("so d: %s\n", *sod)
name, ok := sod.Get("Type").(*PdfObjectName)
if !ok {
common.Log.Error("Object stream should always have a Type")
common.Log.Debug("ERROR: Object stream should always have a Type")
return nil, errors.New("Object stream missing Type")
}
if strings.ToLower(string(*name)) != "objstm" {
common.Log.Error("Object stream type shall always be ObjStm !")
common.Log.Debug("ERROR: Object stream type shall always be ObjStm !")
return nil, errors.New("Object stream type != ObjStm")
}
@ -101,20 +117,20 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject,
// Temporarily change the reader object to this decoded buffer.
// Change back afterwards.
bakOffset := this.GetFileOffset()
defer func() { this.SetFileOffset(bakOffset) }()
bakOffset := parser.GetFileOffset()
defer func() { parser.SetFileOffset(bakOffset) }()
bufReader = bytes.NewReader(ds)
this.reader = bufio.NewReader(bufReader)
parser.reader = bufio.NewReader(bufReader)
common.Log.Trace("Parsing offset map")
// Load the offset map (relative to the beginning of the stream...)
var offsets map[int]int64 = make(map[int]int64)
offsets := map[int]int64{}
// Object list and offsets.
for i := 0; i < int(*N); i++ {
this.skipSpaces()
parser.skipSpaces()
// Object number.
obj, err := this.parseNumber()
obj, err := parser.parseNumber()
if err != nil {
return nil, err
}
@ -123,9 +139,9 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject,
return nil, errors.New("Invalid object stream offset table")
}
this.skipSpaces()
parser.skipSpaces()
// Offset.
obj, err = this.parseNumber()
obj, err = parser.parseNumber()
if err != nil {
return nil, err
}
@ -139,30 +155,30 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject,
}
objstm = ObjectStream{N: int(*N), ds: ds, offsets: offsets}
this.objstms[sobjNumber] = objstm
parser.objstms[sobjNumber] = objstm
} else {
// Temporarily change the reader object to this decoded buffer.
// Point back afterwards.
bakOffset := this.GetFileOffset()
defer func() { this.SetFileOffset(bakOffset) }()
bakOffset := parser.GetFileOffset()
defer func() { parser.SetFileOffset(bakOffset) }()
bufReader = bytes.NewReader(objstm.ds)
// Temporarily change the reader object to this decoded buffer.
this.reader = bufio.NewReader(bufReader)
parser.reader = bufio.NewReader(bufReader)
}
offset := objstm.offsets[objNum]
common.Log.Trace("ACTUAL offset[%d] = %d", objNum, offset)
bufReader.Seek(offset, os.SEEK_SET)
this.reader = bufio.NewReader(bufReader)
parser.reader = bufio.NewReader(bufReader)
bb, _ := this.reader.Peek(100)
bb, _ := parser.reader.Peek(100)
common.Log.Trace("OBJ peek \"%s\"", string(bb))
val, err := this.parseObject()
val, err := parser.parseObject()
if err != nil {
common.Log.Error("Fail to read object (%s)", err)
common.Log.Debug("ERROR Fail to read object (%s)", err)
return nil, err
}
if val == nil {
@ -177,27 +193,26 @@ func (this *PdfParser) lookupObjectViaOS(sobjNumber int, objNum int) (PdfObject,
return &io, nil
}
// Currently a bit messy.. multiple wrappers. Can we clean up?
// Outside interface for lookupByNumberWrapper. Default attempts
// repairs of bad xref tables.
func (this *PdfParser) LookupByNumber(objNumber int) (PdfObject, error) {
obj, _, err := this.lookupByNumberWrapper(objNumber, true)
// LookupByNumber looks up a PdfObject by object number. Returns an error on failure.
// TODO (v3): Unexport.
func (parser *PdfParser) LookupByNumber(objNumber int) (PdfObject, error) {
// Outside interface for lookupByNumberWrapper. Default attempts repairs of bad xref tables.
obj, _, err := parser.lookupByNumberWrapper(objNumber, true)
return obj, err
}
// Wrapper for lookupByNumber, checks if object encrypted etc.
func (this *PdfParser) lookupByNumberWrapper(objNumber int, attemptRepairs bool) (
PdfObject, bool, error) {
obj, inObjStream, err := this.lookupByNumber(objNumber, attemptRepairs)
func (parser *PdfParser) lookupByNumberWrapper(objNumber int, attemptRepairs bool) (PdfObject, bool, error) {
obj, inObjStream, err := parser.lookupByNumber(objNumber, attemptRepairs)
if err != nil {
return nil, inObjStream, err
}
// If encrypted, decrypt it prior to returning.
// Do not attempt to decrypt objects within object streams.
if !inObjStream && this.crypter != nil && !this.crypter.isDecrypted(obj) {
err := this.crypter.Decrypt(obj, 0, 0)
if !inObjStream && parser.crypter != nil && !parser.crypter.isDecrypted(obj) {
err := parser.crypter.Decrypt(obj, 0, 0)
if err != nil {
return nil, inObjStream, err
}
@ -206,7 +221,6 @@ func (this *PdfParser) lookupByNumberWrapper(objNumber int, attemptRepairs bool)
return obj, inObjStream, nil
}
// getObjectNumber returns object number, generation number, error for `obj`
func getObjectNumber(obj PdfObject) (int64, int64, error) {
if io, isIndirect := obj.(*PdfIndirectObject); isIndirect {
return io.ObjectNumber, io.GenerationNumber, nil
@ -219,14 +233,14 @@ func getObjectNumber(obj PdfObject) (int64, int64, error) {
// LookupByNumber
// Repair signals whether to repair if broken.
func (this *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfObject, bool, error) {
obj, ok := this.ObjCache[objNumber]
func (parser *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfObject, bool, error) {
obj, ok := parser.ObjCache[objNumber]
if ok {
common.Log.Trace("Returning cached object %d", objNumber)
return obj, false, nil
}
xref, ok := this.xrefs[objNumber]
xref, ok := parser.xrefs[objNumber]
if !ok {
// An indirect reference to an undefined object shall not be
// considered an error by a conforming reader; it shall be
@ -242,22 +256,22 @@ func (this *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfOb
common.Log.Trace("xrefobj gen %d", xref.generation)
common.Log.Trace("xrefobj offset %d", xref.offset)
this.rs.Seek(xref.offset, os.SEEK_SET)
this.reader = bufio.NewReader(this.rs)
parser.rs.Seek(xref.offset, os.SEEK_SET)
parser.reader = bufio.NewReader(parser.rs)
obj, err := this.ParseIndirectObject()
obj, err := parser.ParseIndirectObject()
if err != nil {
common.Log.Error("Failed reading xref (%s)", err)
common.Log.Debug("ERROR Failed reading xref (%s)", err)
// Offset pointing to a non-object. Try to repair the file.
if attemptRepairs {
common.Log.Debug("Attempting to repair xrefs (top down)")
xrefTable, err := this.repairRebuildXrefsTopDown()
xrefTable, err := parser.repairRebuildXrefsTopDown()
if err != nil {
common.Log.Error("Failed repair (%s)", err)
common.Log.Debug("ERROR Failed repair (%s)", err)
return nil, false, err
}
this.xrefs = *xrefTable
return this.lookupByNumber(objNumber, false)
parser.xrefs = *xrefTable
return parser.lookupByNumber(objNumber, false)
}
return nil, false, err
}
@ -269,19 +283,19 @@ func (this *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfOb
realObjNum, _, _ := getObjectNumber(obj)
if int(realObjNum) != objNumber {
common.Log.Debug("Invalid xrefs: Rebuilding")
err := this.rebuildXrefTable()
err := parser.rebuildXrefTable()
if err != nil {
return nil, false, err
}
// Empty the cache.
this.ObjCache = ObjectCache{}
parser.ObjCache = ObjectCache{}
// Try looking up again and return.
return this.lookupByNumberWrapper(objNumber, false)
return parser.lookupByNumberWrapper(objNumber, false)
}
}
common.Log.Trace("Returning obj")
this.ObjCache[objNumber] = obj
parser.ObjCache[objNumber] = obj
return obj, false, nil
} else if xref.xtype == XREF_OBJECT_STREAM {
common.Log.Trace("xref from object stream!")
@ -289,22 +303,22 @@ func (this *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfOb
common.Log.Trace("Object stream available in object %d/%d", xref.osObjNumber, xref.osObjIndex)
if xref.osObjNumber == objNumber {
common.Log.Error("Circular reference!?!")
common.Log.Debug("ERROR Circular reference!?!")
return nil, true, errors.New("Xref circular reference")
}
_, exists := this.xrefs[xref.osObjNumber]
_, exists := parser.xrefs[xref.osObjNumber]
if exists {
optr, err := this.lookupObjectViaOS(xref.osObjNumber, objNumber) //xref.osObjIndex)
optr, err := parser.lookupObjectViaOS(xref.osObjNumber, objNumber) //xref.osObjIndex)
if err != nil {
common.Log.Error("Returning ERR (%s)", err)
common.Log.Debug("ERROR Returning ERR (%s)", err)
return nil, true, err
}
common.Log.Trace("<Loaded via OS")
this.ObjCache[objNumber] = optr
if this.crypter != nil {
parser.ObjCache[objNumber] = optr
if parser.crypter != nil {
// Mark as decrypted (inside object stream) for caching.
// and avoid decrypting decrypted object.
this.crypter.DecryptedObjects[optr] = true
parser.crypter.DecryptedObjects[optr] = true
}
return optr, true, nil
} else {
@ -315,24 +329,25 @@ func (this *PdfParser) lookupByNumber(objNumber int, attemptRepairs bool) (PdfOb
return nil, false, errors.New("Unknown xref type")
}
// LookupByReference
func (this *PdfParser) LookupByReference(ref PdfObjectReference) (PdfObject, error) {
// LookupByReference looks up a PdfObject by a reference.
func (parser *PdfParser) LookupByReference(ref PdfObjectReference) (PdfObject, error) {
common.Log.Trace("Looking up reference %s", ref.String())
return this.LookupByNumber(int(ref.ObjectNumber))
return parser.LookupByNumber(int(ref.ObjectNumber))
}
// Trace to direct object.
func (this *PdfParser) Trace(obj PdfObject) (PdfObject, error) {
// Trace traces a PdfObject to direct object, looking up and resolving references as needed (unlike TraceToDirect).
// TODO (v3): Unexport.
func (parser *PdfParser) Trace(obj PdfObject) (PdfObject, error) {
ref, isRef := obj.(*PdfObjectReference)
if !isRef {
// Direct object already.
return obj, nil
}
bakOffset := this.GetFileOffset()
defer func() { this.SetFileOffset(bakOffset) }()
bakOffset := parser.GetFileOffset()
defer func() { parser.SetFileOffset(bakOffset) }()
o, err := this.LookupByReference(*ref)
o, err := parser.LookupByReference(*ref)
if err != nil {
return nil, err
}

View File

@ -18,10 +18,9 @@ import (
"github.com/unidoc/unidoc/common"
)
/*
* The PDF standard supports encryption of strings and streams.
* Section 7.6.
*/
// PdfCrypt provides PDF encryption/decryption support.
// The PDF standard supports encryption of strings and streams (Section 7.6).
// TODO (v3): Consider unexporting.
type PdfCrypt struct {
Filter string
Subfilter string
@ -45,6 +44,7 @@ type PdfCrypt struct {
parser *PdfParser
}
// AccessPermissions is a list of access permissions for a PDF file.
type AccessPermissions struct {
Printing bool
Modify bool
@ -66,21 +66,26 @@ const padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF" +
"\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C" +
"\xA9\xFE\x64\x53\x69\x7A"
// CryptFilter represents information from a CryptFilter dictionary.
// TODO (v3): Unexport.
type CryptFilter struct {
Cfm string
Length int
}
// CryptFilters is a map of crypt filter name and underlying CryptFilter info.
// TODO (v3): Unexport.
type CryptFilters map[string]CryptFilter
// Load crypt filter information from the encryption dictionary (V4 only).
func (this *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
this.CryptFilters = CryptFilters{}
// LoadCryptFilters loads crypt filter information from the encryption dictionary (V4 only).
// TODO (v3): Unexport.
func (crypt *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
crypt.CryptFilters = CryptFilters{}
obj := ed.Get("CF")
obj = TraceToDirectObject(obj) // XXX may need to resolve reference...
if ref, isRef := obj.(*PdfObjectReference); isRef {
o, err := this.parser.LookupByReference(*ref)
o, err := crypt.parser.LookupByReference(*ref)
if err != nil {
common.Log.Debug("Error looking up CF reference")
return err
@ -98,7 +103,7 @@ func (this *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
v := cf.Get(name)
if ref, isRef := v.(*PdfObjectReference); isRef {
o, err := this.parser.LookupByReference(*ref)
o, err := crypt.parser.LookupByReference(*ref)
if err != nil {
common.Log.Debug("Error lookup up dictionary reference")
return err
@ -112,7 +117,7 @@ func (this *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
}
if name == "Identity" {
common.Log.Error("- Cannot overwrite the identity filter - Trying next")
common.Log.Debug("ERROR - Cannot overwrite the identity filter - Trying next")
continue
}
@ -163,34 +168,34 @@ func (this *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
cf.Length = int(*length)
}
this.CryptFilters[string(name)] = cf
crypt.CryptFilters[string(name)] = cf
}
// Cannot be overwritten.
this.CryptFilters["Identity"] = CryptFilter{}
crypt.CryptFilters["Identity"] = CryptFilter{}
// StrF strings filter.
this.StringFilter = "Identity"
crypt.StringFilter = "Identity"
if strf, ok := ed.Get("StrF").(*PdfObjectName); ok {
if _, exists := this.CryptFilters[string(*strf)]; !exists {
if _, exists := crypt.CryptFilters[string(*strf)]; !exists {
return fmt.Errorf("Crypt filter for StrF not specified in CF dictionary (%s)", *strf)
}
this.StringFilter = string(*strf)
crypt.StringFilter = string(*strf)
}
// StmF streams filter.
this.StreamFilter = "Identity"
crypt.StreamFilter = "Identity"
if stmf, ok := ed.Get("StmF").(*PdfObjectName); ok {
if _, exists := this.CryptFilters[string(*stmf)]; !exists {
if _, exists := crypt.CryptFilters[string(*stmf)]; !exists {
return fmt.Errorf("Crypt filter for StmF not specified in CF dictionary (%s)", *stmf)
}
this.StreamFilter = string(*stmf)
crypt.StreamFilter = string(*stmf)
}
return nil
}
// Prepare the document crypt handler based on the encryption dictionary
// and trailer dictionary.
// PdfCryptMakeNew makes the document crypt handler based on the encryption dictionary
// and trailer dictionary. Returns an error on failure to process.
func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCrypt, error) {
crypter := PdfCrypt{}
crypter.DecryptedObjects = map[PdfObject]bool{}
@ -200,11 +205,11 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr
filter, ok := ed.Get("Filter").(*PdfObjectName)
if !ok {
common.Log.Error("Crypt dictionary missing required Filter field!")
common.Log.Debug("ERROR Crypt dictionary missing required Filter field!")
return crypter, errors.New("Required crypt field Filter missing")
}
if *filter != "Standard" {
common.Log.Error("Unsupported filter (%s)", *filter)
common.Log.Debug("ERROR Unsupported filter (%s)", *filter)
return crypter, errors.New("Unsupported Filter")
}
crypter.Filter = string(*filter)
@ -217,7 +222,7 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr
if L, ok := ed.Get("Length").(*PdfObjectInteger); ok {
if (*L % 8) != 0 {
common.Log.Error("Invalid encryption length")
common.Log.Debug("ERROR Invalid encryption length")
return crypter, errors.New("Invalid encryption length")
}
crypter.Length = int(*L)
@ -238,7 +243,7 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr
return crypter, err
}
} else {
common.Log.Error("Unsupported encryption algo V = %d", *V)
common.Log.Debug("ERROR Unsupported encryption algo V = %d", *V)
return crypter, errors.New("Unsupported algorithm")
}
} else {
@ -292,7 +297,7 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr
// Strictly, if file is encrypted, the ID should always be specified
// but clearly not everyone is following the specification.
id0 := PdfObjectString("")
if idArray, ok := trailer.Get("ID").(*PdfObjectArray); ok {
if idArray, ok := trailer.Get("ID").(*PdfObjectArray); ok && len(*idArray) >= 1 {
id0obj, ok := (*idArray)[0].(*PdfObjectString)
if !ok {
return crypter, errors.New("Invalid trailer ID")
@ -306,10 +311,11 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr
return crypter, nil
}
func (this *PdfCrypt) GetAccessPermissions() AccessPermissions {
// GetAccessPermissions returns the PDF access permissions as an AccessPermissions object.
func (crypt *PdfCrypt) GetAccessPermissions() AccessPermissions {
perms := AccessPermissions{}
P := this.P
P := crypt.P
if P&(1<<2) > 0 {
perms.Printing = true
}
@ -337,6 +343,7 @@ func (this *PdfCrypt) GetAccessPermissions() AccessPermissions {
return perms
}
// GetP returns the P entry to be used in Encrypt dictionary based on AccessPermissions settings.
func (perms AccessPermissions) GetP() int32 {
var P int32 = 0
@ -367,22 +374,21 @@ func (perms AccessPermissions) GetP() int32 {
return P
}
// Check whether the specified password can be used to decrypt the
// document.
func (this *PdfCrypt) authenticate(password []byte) (bool, error) {
// Check whether the specified password can be used to decrypt the document.
func (crypt *PdfCrypt) authenticate(password []byte) (bool, error) {
// Also build the encryption/decryption key.
this.Authenticated = false
crypt.Authenticated = false
// Try user password.
common.Log.Trace("Debugging authentication - user pass")
authenticated, err := this.Alg6(password)
authenticated, err := crypt.Alg6(password)
if err != nil {
return false, err
}
if authenticated {
common.Log.Trace("this.Authenticated = True")
this.Authenticated = true
crypt.Authenticated = true
return true, nil
}
@ -390,13 +396,13 @@ func (this *PdfCrypt) authenticate(password []byte) (bool, error) {
// May not be necessary if only want to get all contents.
// (user pass needs to be known or empty).
common.Log.Trace("Debugging authentication - owner pass")
authenticated, err = this.Alg7(password)
authenticated, err = crypt.Alg7(password)
if err != nil {
return false, err
}
if authenticated {
common.Log.Trace("this.Authenticated = True")
this.Authenticated = true
crypt.Authenticated = true
return true, nil
}
@ -409,11 +415,11 @@ func (this *PdfCrypt) authenticate(password []byte) (bool, error) {
// The bool flag indicates that the user can access and can view the file.
// The AccessPermissions shows what access the user has for editing etc.
// An error is returned if there was a problem performing the authentication.
func (this *PdfCrypt) checkAccessRights(password []byte) (bool, AccessPermissions, error) {
func (crypt *PdfCrypt) checkAccessRights(password []byte) (bool, AccessPermissions, error) {
perms := AccessPermissions{}
// Try owner password -> full rights.
isOwner, err := this.Alg7(password)
isOwner, err := crypt.Alg7(password)
if err != nil {
return false, perms, err
}
@ -431,20 +437,20 @@ func (this *PdfCrypt) checkAccessRights(password []byte) (bool, AccessPermission
}
// Try user password.
isUser, err := this.Alg6(password)
isUser, err := crypt.Alg6(password)
if err != nil {
return false, perms, err
}
if isUser {
// User password specified correctly -> access granted with specified permissions.
return true, this.GetAccessPermissions(), nil
return true, crypt.GetAccessPermissions(), nil
}
// Cannot even view the file.
return false, perms, nil
}
func (this *PdfCrypt) paddedPass(pass []byte) []byte {
func (crypt *PdfCrypt) paddedPass(pass []byte) []byte {
key := make([]byte, 32)
if len(pass) >= 32 {
for i := 0; i < 32; i++ {
@ -463,10 +469,10 @@ func (this *PdfCrypt) paddedPass(pass []byte) []byte {
// Generates a key for encrypting a specific object based on the
// object and generation number, as well as the document encryption key.
func (this *PdfCrypt) makeKey(filter string, objNum, genNum uint32, ekey []byte) ([]byte, error) {
cf, ok := this.CryptFilters[filter]
func (crypt *PdfCrypt) makeKey(filter string, objNum, genNum uint32, ekey []byte) ([]byte, error) {
cf, ok := crypt.CryptFilters[filter]
if !ok {
common.Log.Error("Unsupported crypt filter (%s)", filter)
common.Log.Debug("ERROR Unsupported crypt filter (%s)", filter)
return nil, fmt.Errorf("Unsupported crypt filter (%s)", filter)
}
isAES := false
@ -503,29 +509,29 @@ func (this *PdfCrypt) makeKey(filter string, objNum, genNum uint32, ekey []byte)
if len(ekey)+5 < 16 {
return hashb[0 : len(ekey)+5], nil
} else {
return hashb, nil
}
return hashb, nil
}
// Check if object has already been processed.
func (this *PdfCrypt) isDecrypted(obj PdfObject) bool {
_, ok := this.DecryptedObjects[obj]
func (crypt *PdfCrypt) isDecrypted(obj PdfObject) bool {
_, ok := crypt.DecryptedObjects[obj]
if ok {
common.Log.Trace("Already decrypted")
return true
} else {
common.Log.Trace("Not decrypted yet")
return false
}
common.Log.Trace("Not decrypted yet")
return false
}
// Decrypt a buffer with a selected crypt filter.
func (this *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) {
func (crypt *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) {
common.Log.Trace("Decrypt bytes")
cf, ok := this.CryptFilters[filter]
cf, ok := crypt.CryptFilters[filter]
if !ok {
common.Log.Error("Unsupported crypt filter (%s)", filter)
common.Log.Debug("ERROR Unsupported crypt filter (%s)", filter)
return nil, fmt.Errorf("Unsupported crypt filter (%s)", filter)
}
@ -565,7 +571,7 @@ func (this *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]by
// vector is a 16-byte random number that is stored as the first
// 16 bytes of the encrypted stream or string.
if len(buf) < 16 {
common.Log.Error("AES invalid buf %s", buf)
common.Log.Debug("ERROR AES invalid buf %s", buf)
return buf, fmt.Errorf("AES: Buf len < 16 (%d)", len(buf))
}
@ -609,20 +615,20 @@ func (this *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]by
// Traverses through all the subobjects (recursive).
//
// Does not look up references.. That should be done prior to calling.
func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) error {
if this.isDecrypted(obj) {
func (crypt *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) error {
if crypt.isDecrypted(obj) {
return nil
}
if io, isIndirect := obj.(*PdfIndirectObject); isIndirect {
this.DecryptedObjects[io] = true
crypt.DecryptedObjects[io] = true
common.Log.Trace("Decrypting indirect %d %d obj!", io.ObjectNumber, io.GenerationNumber)
objNum := (*io).ObjectNumber
genNum := (*io).GenerationNumber
err := this.Decrypt(io.PdfObject, objNum, genNum)
err := crypt.Decrypt(io.PdfObject, objNum, genNum)
if err != nil {
return err
}
@ -632,7 +638,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
if so, isStream := obj.(*PdfObjectStream); isStream {
// Mark as decrypted first to avoid recursive issues.
this.DecryptedObjects[so] = true
crypt.DecryptedObjects[so] = true
objNum := (*so).ObjectNumber
genNum := (*so).GenerationNumber
common.Log.Trace("Decrypting stream %d %d !", objNum, genNum)
@ -643,9 +649,9 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
dict := so.PdfObjectDictionary
streamFilter := "Default" // Default RC4.
if this.V >= 4 {
streamFilter = this.StreamFilter
common.Log.Trace("this.StreamFilter = %s", this.StreamFilter)
if crypt.V >= 4 {
streamFilter = crypt.StreamFilter
common.Log.Trace("this.StreamFilter = %s", crypt.StreamFilter)
if filters, ok := dict.Get("Filter").(*PdfObjectArray); ok {
// Crypt filter can only be the first entry.
@ -658,7 +664,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
// Check if valid crypt filter specified in the decode params.
if decodeParams, ok := dict.Get("DecodeParms").(*PdfObjectDictionary); ok {
if filterName, ok := decodeParams.Get("Name").(*PdfObjectName); ok {
if _, ok := this.CryptFilters[string(*filterName)]; ok {
if _, ok := crypt.CryptFilters[string(*filterName)]; ok {
common.Log.Trace("Using stream filter %s", *filterName)
streamFilter = string(*filterName)
}
@ -675,17 +681,17 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
}
}
err := this.Decrypt(so.PdfObjectDictionary, objNum, genNum)
err := crypt.Decrypt(so.PdfObjectDictionary, objNum, genNum)
if err != nil {
return err
}
okey, err := this.makeKey(streamFilter, uint32(objNum), uint32(genNum), this.EncryptionKey)
okey, err := crypt.makeKey(streamFilter, uint32(objNum), uint32(genNum), crypt.EncryptionKey)
if err != nil {
return err
}
so.Stream, err = this.decryptBytes(so.Stream, streamFilter, okey)
so.Stream, err = crypt.decryptBytes(so.Stream, streamFilter, okey)
if err != nil {
return err
}
@ -698,18 +704,18 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
common.Log.Trace("Decrypting string!")
stringFilter := "Default"
if this.V >= 4 {
if crypt.V >= 4 {
// Currently only support Identity / RC4.
common.Log.Trace("with %s filter", this.StringFilter)
if this.StringFilter == "Identity" {
common.Log.Trace("with %s filter", crypt.StringFilter)
if crypt.StringFilter == "Identity" {
// Identity: pass unchanged: No action.
return nil
} else {
stringFilter = this.StringFilter
stringFilter = crypt.StringFilter
}
}
key, err := this.makeKey(stringFilter, uint32(parentObjNum), uint32(parentGenNum), this.EncryptionKey)
key, err := crypt.makeKey(stringFilter, uint32(parentObjNum), uint32(parentGenNum), crypt.EncryptionKey)
if err != nil {
return err
}
@ -720,7 +726,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
decrypted[i] = (*s)[i]
}
common.Log.Trace("Decrypt string: %s : % x", decrypted, decrypted)
decrypted, err = this.decryptBytes(decrypted, stringFilter, key)
decrypted, err = crypt.decryptBytes(decrypted, stringFilter, key)
if err != nil {
return err
}
@ -731,7 +737,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
if a, isArray := obj.(*PdfObjectArray); isArray {
for _, o := range *a {
err := this.Decrypt(o, parentObjNum, parentGenNum)
err := crypt.Decrypt(o, parentObjNum, parentGenNum)
if err != nil {
return err
}
@ -757,7 +763,7 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
}
if string(keyidx) != "Parent" && string(keyidx) != "Prev" && string(keyidx) != "Last" { // Check not needed?
err := this.Decrypt(o, parentObjNum, parentGenNum)
err := crypt.Decrypt(o, parentObjNum, parentGenNum)
if err != nil {
return err
}
@ -770,23 +776,23 @@ func (this *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
}
// Check if object has already been processed.
func (this *PdfCrypt) isEncrypted(obj PdfObject) bool {
_, ok := this.EncryptedObjects[obj]
func (crypt *PdfCrypt) isEncrypted(obj PdfObject) bool {
_, ok := crypt.EncryptedObjects[obj]
if ok {
common.Log.Trace("Already encrypted")
return true
} else {
common.Log.Trace("Not encrypted yet")
return false
}
common.Log.Trace("Not encrypted yet")
return false
}
// Encrypt a buffer with the specified crypt filter and key.
func (this *PdfCrypt) encryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) {
func (crypt *PdfCrypt) encryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) {
common.Log.Trace("Encrypt bytes")
cf, ok := this.CryptFilters[filter]
cf, ok := crypt.CryptFilters[filter]
if !ok {
common.Log.Error("Unsupported crypt filter (%s)", filter)
common.Log.Debug("ERROR Unsupported crypt filter (%s)", filter)
return nil, fmt.Errorf("Unsupported crypt filter (%s)", filter)
}
@ -857,20 +863,20 @@ func (this *PdfCrypt) encryptBytes(buf []byte, filter string, okey []byte) ([]by
// Traverses through all the subobjects (recursive).
//
// Does not look up references.. That should be done prior to calling.
func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) error {
if this.isEncrypted(obj) {
func (crypt *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) error {
if crypt.isEncrypted(obj) {
return nil
}
if io, isIndirect := obj.(*PdfIndirectObject); isIndirect {
this.EncryptedObjects[io] = true
crypt.EncryptedObjects[io] = true
common.Log.Trace("Encrypting indirect %d %d obj!", io.ObjectNumber, io.GenerationNumber)
objNum := (*io).ObjectNumber
genNum := (*io).GenerationNumber
err := this.Encrypt(io.PdfObject, objNum, genNum)
err := crypt.Encrypt(io.PdfObject, objNum, genNum)
if err != nil {
return err
}
@ -879,7 +885,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
}
if so, isStream := obj.(*PdfObjectStream); isStream {
this.EncryptedObjects[so] = true
crypt.EncryptedObjects[so] = true
objNum := (*so).ObjectNumber
genNum := (*so).GenerationNumber
common.Log.Trace("Encrypting stream %d %d !", objNum, genNum)
@ -890,11 +896,11 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
dict := so.PdfObjectDictionary
streamFilter := "Default" // Default RC4.
if this.V >= 4 {
if crypt.V >= 4 {
// For now. Need to change when we add support for more than
// Identity / RC4.
streamFilter = this.StreamFilter
common.Log.Trace("this.StreamFilter = %s", this.StreamFilter)
streamFilter = crypt.StreamFilter
common.Log.Trace("this.StreamFilter = %s", crypt.StreamFilter)
if filters, ok := dict.Get("Filter").(*PdfObjectArray); ok {
// Crypt filter can only be the first entry.
@ -907,7 +913,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
// Check if valid crypt filter specified in the decode params.
if decodeParams, ok := dict.Get("DecodeParms").(*PdfObjectDictionary); ok {
if filterName, ok := decodeParams.Get("Name").(*PdfObjectName); ok {
if _, ok := this.CryptFilters[string(*filterName)]; ok {
if _, ok := crypt.CryptFilters[string(*filterName)]; ok {
common.Log.Trace("Using stream filter %s", *filterName)
streamFilter = string(*filterName)
}
@ -924,17 +930,17 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
}
}
err := this.Encrypt(so.PdfObjectDictionary, objNum, genNum)
err := crypt.Encrypt(so.PdfObjectDictionary, objNum, genNum)
if err != nil {
return err
}
okey, err := this.makeKey(streamFilter, uint32(objNum), uint32(genNum), this.EncryptionKey)
okey, err := crypt.makeKey(streamFilter, uint32(objNum), uint32(genNum), crypt.EncryptionKey)
if err != nil {
return err
}
so.Stream, err = this.encryptBytes(so.Stream, streamFilter, okey)
so.Stream, err = crypt.encryptBytes(so.Stream, streamFilter, okey)
if err != nil {
return err
}
@ -947,17 +953,17 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
common.Log.Trace("Encrypting string!")
stringFilter := "Default"
if this.V >= 4 {
common.Log.Trace("with %s filter", this.StringFilter)
if this.StringFilter == "Identity" {
if crypt.V >= 4 {
common.Log.Trace("with %s filter", crypt.StringFilter)
if crypt.StringFilter == "Identity" {
// Identity: pass unchanged: No action.
return nil
} else {
stringFilter = this.StringFilter
stringFilter = crypt.StringFilter
}
}
key, err := this.makeKey(stringFilter, uint32(parentObjNum), uint32(parentGenNum), this.EncryptionKey)
key, err := crypt.makeKey(stringFilter, uint32(parentObjNum), uint32(parentGenNum), crypt.EncryptionKey)
if err != nil {
return err
}
@ -967,7 +973,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
encrypted[i] = (*s)[i]
}
common.Log.Trace("Encrypt string: %s : % x", encrypted, encrypted)
encrypted, err = this.encryptBytes(encrypted, stringFilter, key)
encrypted, err = crypt.encryptBytes(encrypted, stringFilter, key)
if err != nil {
return err
}
@ -978,7 +984,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
if a, isArray := obj.(*PdfObjectArray); isArray {
for _, o := range *a {
err := this.Encrypt(o, parentObjNum, parentGenNum)
err := crypt.Encrypt(o, parentObjNum, parentGenNum)
if err != nil {
return err
}
@ -1004,7 +1010,7 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
continue
}
if string(keyidx) != "Parent" && string(keyidx) != "Prev" && string(keyidx) != "Last" { // Check not needed?
err := this.Encrypt(o, parentObjNum, parentGenNum)
err := crypt.Encrypt(o, parentObjNum, parentGenNum)
if err != nil {
return err
}
@ -1016,19 +1022,20 @@ func (this *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) e
return nil
}
// Algorithm 2: Computing an encryption key.
func (this *PdfCrypt) Alg2(pass []byte) []byte {
// Alg2 computes an encryption key.
// TODO (v3): Unexport.
func (crypt *PdfCrypt) Alg2(pass []byte) []byte {
common.Log.Trace("Alg2")
key := this.paddedPass(pass)
key := crypt.paddedPass(pass)
h := md5.New()
h.Write(key)
// Pass O.
h.Write(this.O)
h.Write(crypt.O)
// Pass P (Lower order byte first).
var p uint32 = uint32(this.P)
var p uint32 = uint32(crypt.P)
var pb = []byte{}
for i := 0; i < 4; i++ {
pb = append(pb, byte(((p >> uint(8*i)) & 0xff)))
@ -1037,36 +1044,36 @@ func (this *PdfCrypt) Alg2(pass []byte) []byte {
common.Log.Trace("go P: % x", pb)
// Pass ID[0] from the trailer
h.Write([]byte(this.Id0))
h.Write([]byte(crypt.Id0))
common.Log.Trace("this.R = %d encryptMetadata %v", this.R, this.EncryptMetadata)
if (this.R >= 4) && !this.EncryptMetadata {
common.Log.Trace("this.R = %d encryptMetadata %v", crypt.R, crypt.EncryptMetadata)
if (crypt.R >= 4) && !crypt.EncryptMetadata {
h.Write([]byte{0xff, 0xff, 0xff, 0xff})
}
hashb := h.Sum(nil)
if this.R >= 3 {
if crypt.R >= 3 {
for i := 0; i < 50; i++ {
h = md5.New()
h.Write(hashb[0 : this.Length/8])
h.Write(hashb[0 : crypt.Length/8])
hashb = h.Sum(nil)
}
}
if this.R >= 3 {
return hashb[0 : this.Length/8]
if crypt.R >= 3 {
return hashb[0 : crypt.Length/8]
}
return hashb[0:5]
}
// Create the RC4 encryption key.
func (this *PdfCrypt) alg3_key(pass []byte) []byte {
func (crypt *PdfCrypt) alg3Key(pass []byte) []byte {
h := md5.New()
okey := this.paddedPass(pass)
okey := crypt.paddedPass(pass)
h.Write(okey)
if this.R >= 3 {
if crypt.R >= 3 {
for i := 0; i < 50; i++ {
hashb := h.Sum(nil)
h = md5.New()
@ -1075,25 +1082,25 @@ func (this *PdfCrypt) alg3_key(pass []byte) []byte {
}
encKey := h.Sum(nil)
if this.R == 2 {
if crypt.R == 2 {
encKey = encKey[0:5]
} else {
encKey = encKey[0 : this.Length/8]
encKey = encKey[0 : crypt.Length/8]
}
return encKey
}
// Algorithm 3: Computing the encryption dictionarys O
// (owner password) value.
func (this *PdfCrypt) Alg3(upass, opass []byte) (PdfObjectString, error) {
// Alg3 computes the encryption dictionarys O (owner password) value.
// TODO (v3): Unexport.
func (crypt *PdfCrypt) Alg3(upass, opass []byte) (PdfObjectString, error) {
// Return O string val.
O := PdfObjectString("")
var encKey []byte
if len(opass) > 0 {
encKey = this.alg3_key(opass)
encKey = crypt.alg3Key(opass)
} else {
encKey = this.alg3_key(upass)
encKey = crypt.alg3Key(upass)
}
ociph, err := rc4.NewCipher(encKey)
@ -1101,11 +1108,11 @@ func (this *PdfCrypt) Alg3(upass, opass []byte) (PdfObjectString, error) {
return O, errors.New("Failed rc4 ciph")
}
ukey := this.paddedPass(upass)
ukey := crypt.paddedPass(upass)
encrypted := make([]byte, len(ukey))
ociph.XORKeyStream(encrypted, ukey)
if this.R >= 3 {
if crypt.R >= 3 {
encKey2 := make([]byte, len(encKey))
for i := 0; i < 19; i++ {
for j := 0; j < len(encKey); j++ {
@ -1123,12 +1130,12 @@ func (this *PdfCrypt) Alg3(upass, opass []byte) (PdfObjectString, error) {
return O, nil
}
// Algorithm 4: Computing the encryption dictionarys U (user password)
// value (Security handlers of revision 2).
func (this *PdfCrypt) Alg4(upass []byte) (PdfObjectString, []byte, error) {
// Alg4 computes the encryption dictionarys U (user password) value (Security handlers of revision 2).
// TODO (v3): Unexport.
func (crypt *PdfCrypt) Alg4(upass []byte) (PdfObjectString, []byte, error) {
U := PdfObjectString("")
ekey := this.Alg2(upass)
ekey := crypt.Alg2(upass)
ciph, err := rc4.NewCipher(ekey)
if err != nil {
return U, ekey, errors.New("Failed rc4 ciph")
@ -1142,21 +1149,21 @@ func (this *PdfCrypt) Alg4(upass []byte) (PdfObjectString, []byte, error) {
return U, ekey, nil
}
// Algorithm 5: Computing the encryption dictionarys U (user password)
// value (Security handlers of revision 3 or greater).
func (this *PdfCrypt) Alg5(upass []byte) (PdfObjectString, []byte, error) {
// Alg5 computes the encryption dictionarys U (user password) value (Security handlers of revision 3 or greater).
// TODO (v3): Unexport.
func (crypt *PdfCrypt) Alg5(upass []byte) (PdfObjectString, []byte, error) {
U := PdfObjectString("")
ekey := this.Alg2(upass)
ekey := crypt.Alg2(upass)
h := md5.New()
h.Write([]byte(padding))
h.Write([]byte(this.Id0))
h.Write([]byte(crypt.Id0))
hash := h.Sum(nil)
common.Log.Trace("Alg5")
common.Log.Trace("ekey: % x", ekey)
common.Log.Trace("ID: % x", this.Id0)
common.Log.Trace("ID: % x", crypt.Id0)
if len(hash) != 16 {
return U, ekey, errors.New("Hash length not 16 bytes")
@ -1206,15 +1213,16 @@ func (this *PdfCrypt) Alg5(upass []byte) (PdfObjectString, []byte, error) {
return U, ekey, nil
}
// Algorithm 6: Authenticating the user password
func (this *PdfCrypt) Alg6(upass []byte) (bool, error) {
// Alg6 authenticates the user password.
// TODO (v3): Unexport.
func (crypt *PdfCrypt) Alg6(upass []byte) (bool, error) {
var uo PdfObjectString
var err error
var key []byte
if this.R == 2 {
uo, key, err = this.Alg4(upass)
} else if this.R >= 3 {
uo, key, err = this.Alg5(upass)
if crypt.R == 2 {
uo, key, err = crypt.Alg4(upass)
} else if crypt.R >= 3 {
uo, key, err = crypt.Alg5(upass)
} else {
return false, errors.New("invalid R")
}
@ -1223,37 +1231,43 @@ func (this *PdfCrypt) Alg6(upass []byte) (bool, error) {
return false, err
}
common.Log.Trace("check: % x == % x ?", string(uo), string(this.U))
common.Log.Trace("check: % x == % x ?", string(uo), string(crypt.U))
uGen := string(uo) // Generated U from specified pass.
uDoc := string(this.U) // U from the document.
if this.R >= 3 {
uGen := string(uo) // Generated U from specified pass.
uDoc := string(crypt.U) // U from the document.
if crypt.R >= 3 {
// comparing on the first 16 bytes in the case of security
// handlers of revision 3 or greater),
uGen = uGen[0:16]
uDoc = uDoc[0:16]
if len(uGen) > 16 {
uGen = uGen[0:16]
}
if len(uDoc) > 16 {
uDoc = uDoc[0:16]
}
}
if uGen == uDoc {
this.EncryptionKey = key
crypt.EncryptionKey = key
return true, nil
} else {
return false, nil
}
return false, nil
}
// Algorithm 7: Authenticating the owner password.
func (this *PdfCrypt) Alg7(opass []byte) (bool, error) {
encKey := this.alg3_key(opass)
// Alg7 authenticates the owner password.
// TODO (v3): Unexport.
func (crypt *PdfCrypt) Alg7(opass []byte) (bool, error) {
encKey := crypt.alg3Key(opass)
decrypted := make([]byte, len(this.O))
if this.R == 2 {
decrypted := make([]byte, len(crypt.O))
if crypt.R == 2 {
ciph, err := rc4.NewCipher(encKey)
if err != nil {
return false, errors.New("Failed cipher")
}
ciph.XORKeyStream(decrypted, this.O)
} else if this.R >= 3 {
s := append([]byte{}, this.O...)
ciph.XORKeyStream(decrypted, crypt.O)
} else if crypt.R >= 3 {
s := append([]byte{}, crypt.O...)
for i := 0; i < 20; i++ {
//newKey := encKey
newKey := append([]byte{}, encKey...)
@ -1271,7 +1285,7 @@ func (this *PdfCrypt) Alg7(opass []byte) (bool, error) {
return false, errors.New("invalid R")
}
auth, err := this.Alg6(decrypted)
auth, err := crypt.Alg6(decrypted)
if err != nil {
return false, nil
}

View File

@ -179,7 +179,7 @@ func TestDecryption1(t *testing.T) {
parser := PdfParser{}
parser.xrefs = make(XrefTable)
parser.objstms = make(ObjectStreams)
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
parser.crypter = &crypter
obj, err := parser.ParseIndirectObject()

9
pdf/core/doc.go Normal file
View File

@ -0,0 +1,9 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
// Package core defines and implements the primitive PDF object types in golang, and provides functionality
// for parsing those from a PDF file stream. This includes I/O handling, cross references, repairs, encryption,
// encoding and other core capabilities.
package core

View File

@ -10,8 +10,12 @@ package core
// - FlateDecode
// - LZW
// - DCT Decode (JPEG)
// - RunLength
// - ASCII Hex
// - ASCII85
// - CCITT Fax (dummy)
// - JBIG2 (dummy)
// - JPX (dummy)
import (
"bytes"
@ -33,12 +37,16 @@ import (
)
const (
StreamEncodingFilterNameFlate = "FlateDecode"
StreamEncodingFilterNameLZW = "LZWDecode"
StreamEncodingFilterNameDCT = "DCTDecode"
StreamEncodingFilterNameASCIIHex = "ASCIIHexDecode"
StreamEncodingFilterNameASCII85 = "ASCII85Decode"
StreamEncodingFilterNameRaw = "Raw"
StreamEncodingFilterNameFlate = "FlateDecode"
StreamEncodingFilterNameLZW = "LZWDecode"
StreamEncodingFilterNameDCT = "DCTDecode"
StreamEncodingFilterNameRunLength = "RunLengthDecode"
StreamEncodingFilterNameASCIIHex = "ASCIIHexDecode"
StreamEncodingFilterNameASCII85 = "ASCII85Decode"
StreamEncodingFilterNameCCITTFax = "CCITTFaxDecode"
StreamEncodingFilterNameJBIG2 = "JBIG2Decode"
StreamEncodingFilterNameJPX = "JPXDecode"
StreamEncodingFilterNameRaw = "Raw"
)
const (
@ -141,8 +149,15 @@ func newFlateEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObje
// If decodeParams not provided, see if we can get from the stream.
if decodeParams == nil {
obj := encDict.Get("DecodeParms")
obj := TraceToDirectObject(encDict.Get("DecodeParms"))
if obj != nil {
if arr, isArr := obj.(*PdfObjectArray); isArr {
if len(*arr) != 1 {
common.Log.Debug("Error: DecodeParms array length != 1 (%d)", len(*arr))
return nil, errors.New("Range check error")
}
obj = TraceToDirectObject((*arr)[0])
}
dp, isDict := obj.(*PdfObjectDictionary)
if !isDict {
common.Log.Debug("Error: DecodeParms not a dictionary (%T)", obj)
@ -237,7 +252,7 @@ func (this *FlateEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, erro
common.Log.Trace("FlateDecode stream")
common.Log.Trace("Predictor: %d", this.Predictor)
if this.BitsPerComponent != 8 {
return nil, fmt.Errorf("Invalid BitsPerComponent (only 8 supported)")
return nil, fmt.Errorf("Invalid BitsPerComponent=%d (only 8 supported)", this.BitsPerComponent)
}
outData, err := this.DecodeBytes(streamObj.Stream)
@ -253,18 +268,21 @@ func (this *FlateEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, erro
common.Log.Trace("Colors: %d", this.Colors)
rowLength := int(this.Columns) * this.Colors
if rowLength < 1 {
// No data. Return empty set.
return []byte{}, nil
}
rows := len(outData) / rowLength
if len(outData)%rowLength != 0 {
common.Log.Error("TIFF encoding: Invalid row length (%d/%d)",
len(outData), rowLength)
common.Log.Debug("ERROR: TIFF encoding: Invalid row length...")
return nil, fmt.Errorf("Invalid row length (%d/%d)", len(outData), rowLength)
}
if rowLength%this.Colors != 0 {
common.Log.Error("Invalid row length (%d) for colors %d",
rowLength, this.Colors)
return nil, fmt.Errorf("Invalid row length (%d) for colors %d",
rowLength, this.Colors)
return nil, fmt.Errorf("Invalid row length (%d) for colors %d", rowLength, this.Colors)
}
if rowLength > len(outData) {
common.Log.Debug("Row length cannot be longer than data length (%d/%d)", rowLength, len(outData))
return nil, errors.New("Range check error")
}
common.Log.Trace("inp outData (%d): % x", len(outData), outData)
@ -290,9 +308,12 @@ func (this *FlateEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, erro
rowLength := int(this.Columns*this.Colors + 1) // 1 byte to specify predictor algorithms per row.
rows := len(outData) / rowLength
if len(outData)%rowLength != 0 {
common.Log.Error("Invalid row length (%d/%d)", len(outData), rowLength)
return nil, fmt.Errorf("Invalid row length (%d/%d)", len(outData), rowLength)
}
if rowLength > len(outData) {
common.Log.Debug("Row length cannot be longer than data length (%d/%d)", rowLength, len(outData))
return nil, errors.New("Range check error")
}
pOutBuffer := bytes.NewBuffer(nil)
@ -508,12 +529,19 @@ func newLZWEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObject
if decodeParams == nil {
obj := encDict.Get("DecodeParms")
if obj != nil {
dp, isDict := obj.(*PdfObjectDictionary)
if !isDict {
common.Log.Debug("Error: DecodeParms not a dictionary (%T)", obj)
if dp, isDict := obj.(*PdfObjectDictionary); isDict {
decodeParams = dp
} else if a, isArr := obj.(*PdfObjectArray); isArr {
if len(*a) == 1 {
if dp, isDict := (*a)[0].(*PdfObjectDictionary); isDict {
decodeParams = dp
}
}
}
if decodeParams == nil {
common.Log.Error("DecodeParms not a dictionary %#v", obj)
return nil, fmt.Errorf("Invalid DecodeParms")
}
decodeParams = dp
}
}
@ -638,15 +666,25 @@ func (this *LZWEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error)
common.Log.Trace("Tiff encoding")
rowLength := int(this.Columns) * this.Colors
if rowLength < 1 {
// No data. Return empty set.
return []byte{}, nil
}
rows := len(outData) / rowLength
if len(outData)%rowLength != 0 {
common.Log.Error("TIFF encoding: Invalid row length...")
common.Log.Debug("ERROR: TIFF encoding: Invalid row length...")
return nil, fmt.Errorf("Invalid row length (%d/%d)", len(outData), rowLength)
}
if rowLength%this.Colors != 0 {
return nil, fmt.Errorf("Invalid row length (%d) for colors %d", rowLength, this.Colors)
}
if rowLength > len(outData) {
common.Log.Debug("Row length cannot be longer than data length (%d/%d)", rowLength, len(outData))
return nil, errors.New("Range check error")
}
common.Log.Trace("inp outData (%d): % x", len(outData), outData)
pOutBuffer := bytes.NewBuffer(nil)
@ -671,11 +709,18 @@ func (this *LZWEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error)
// Columns represents the number of samples per row; Each sample can contain multiple color
// components.
rowLength := int(this.Columns*this.Colors + 1) // 1 byte to specify predictor algorithms per row.
if rowLength < 1 {
// No data. Return empty set.
return []byte{}, nil
}
rows := len(outData) / rowLength
if len(outData)%rowLength != 0 {
common.Log.Error("Invalid row length (%d/%d)", len(outData), rowLength)
return nil, fmt.Errorf("Invalid row length (%d/%d)", len(outData), rowLength)
}
if rowLength > len(outData) {
common.Log.Debug("Row length cannot be longer than data length (%d/%d)", rowLength, len(outData))
return nil, errors.New("Range check error")
}
pOutBuffer := bytes.NewBuffer(nil)
@ -1055,6 +1100,148 @@ func (this *DCTEncoder) EncodeBytes(data []byte) ([]byte, error) {
return buf.Bytes(), nil
}
// Run length encoding.
type RunLengthEncoder struct {
}
// Make a new run length encoder
func NewRunLengthEncoder() *RunLengthEncoder {
return &RunLengthEncoder{}
}
func (this *RunLengthEncoder) GetFilterName() string {
return StreamEncodingFilterNameRunLength
}
// Create a new run length decoder from a stream object.
func newRunLengthEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObjectDictionary) (*RunLengthEncoder, error) {
return NewRunLengthEncoder(), nil
}
/*
7.4.5 RunLengthDecode Filter
The RunLengthDecode filter decodes data that has been encoded in a simple byte-oriented format based on run length.
The encoded data shall be a sequence of runs, where each run shall consist of a length byte followed by 1 to 128
bytes of data. If the length byte is in the range 0 to 127, the following length + 1 (1 to 128) bytes shall be
copied literally during decompression. If length is in the range 129 to 255, the following single byte shall be
copied 257 - length (2 to 128) times during decompression. A length value of 128 shall denote EOD.
*/
func (this *RunLengthEncoder) DecodeBytes(encoded []byte) ([]byte, error) {
bufReader := bytes.NewReader(encoded)
inb := []byte{}
for {
b, err := bufReader.ReadByte()
if err != nil {
return nil, err
}
if b > 128 {
v, err := bufReader.ReadByte()
if err != nil {
return nil, err
}
for i := 0; i < 257-int(b); i++ {
inb = append(inb, v)
}
} else if b < 128 {
for i := 0; i < int(b)+1; i++ {
v, err := bufReader.ReadByte()
if err != nil {
return nil, err
}
inb = append(inb, v)
}
} else {
break
}
}
return inb, nil
}
// Decode RunLengthEncoded stream object and give back decoded bytes.
func (this *RunLengthEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) {
return this.DecodeBytes(streamObj.Stream)
}
// Encode a bytes array and return the encoded value based on the encoder parameters.
func (this *RunLengthEncoder) EncodeBytes(data []byte) ([]byte, error) {
bufReader := bytes.NewReader(data)
inb := []byte{}
literal := []byte{}
b0, err := bufReader.ReadByte()
if err == io.EOF {
return []byte{}, nil
} else if err != nil {
return nil, err
}
runLen := 1
for {
b, err := bufReader.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if b == b0 {
if len(literal) > 0 {
literal = literal[:len(literal)-1]
if len(literal) > 0 {
inb = append(inb, byte(len(literal)-1))
inb = append(inb, literal...)
}
runLen = 1
literal = []byte{}
}
runLen++
if runLen >= 127 {
inb = append(inb, byte(257-runLen), b0)
runLen = 0
}
} else {
if runLen > 0 {
if runLen == 1 {
literal = []byte{b0}
} else {
inb = append(inb, byte(257-runLen), b0)
}
runLen = 0
}
literal = append(literal, b)
if len(literal) >= 127 {
inb = append(inb, byte(len(literal)-1))
inb = append(inb, literal...)
literal = []byte{}
}
}
b0 = b
}
if len(literal) > 0 {
inb = append(inb, byte(len(literal)-1))
inb = append(inb, literal...)
} else if runLen > 0 {
inb = append(inb, byte(257-runLen), b0)
}
inb = append(inb, 128)
return inb, nil
}
func (this *RunLengthEncoder) MakeDecodeParams() PdfObject {
return nil
}
// Make a new instance of an encoding dictionary for a stream object.
func (this *RunLengthEncoder) MakeStreamDict() *PdfObjectDictionary {
dict := MakeDict()
dict.Set("Filter", MakeName(this.GetFilterName()))
return dict
}
/////
// ASCII hex encoder/decoder.
type ASCIIHexEncoder struct {
@ -1336,6 +1523,117 @@ func (this *RawEncoder) EncodeBytes(data []byte) ([]byte, error) {
return data, nil
}
//
// CCITTFax encoder/decoder (dummy, for now)
//
type CCITTFaxEncoder struct{}
func NewCCITTFaxEncoder() *CCITTFaxEncoder {
return &CCITTFaxEncoder{}
}
func (this *CCITTFaxEncoder) GetFilterName() string {
return StreamEncodingFilterNameCCITTFax
}
func (this *CCITTFaxEncoder) MakeDecodeParams() PdfObject {
return nil
}
// Make a new instance of an encoding dictionary for a stream object.
func (this *CCITTFaxEncoder) MakeStreamDict() *PdfObjectDictionary {
return MakeDict()
}
func (this *CCITTFaxEncoder) DecodeBytes(encoded []byte) ([]byte, error) {
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
return encoded, ErrNoCCITTFaxDecode
}
func (this *CCITTFaxEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) {
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
return streamObj.Stream, ErrNoCCITTFaxDecode
}
func (this *CCITTFaxEncoder) EncodeBytes(data []byte) ([]byte, error) {
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
return data, ErrNoCCITTFaxDecode
}
//
// JBIG2 encoder/decoder (dummy, for now)
//
type JBIG2Encoder struct{}
func NewJBIG2Encoder() *JBIG2Encoder {
return &JBIG2Encoder{}
}
func (this *JBIG2Encoder) GetFilterName() string {
return StreamEncodingFilterNameJBIG2
}
func (this *JBIG2Encoder) MakeDecodeParams() PdfObject {
return nil
}
// Make a new instance of an encoding dictionary for a stream object.
func (this *JBIG2Encoder) MakeStreamDict() *PdfObjectDictionary {
return MakeDict()
}
func (this *JBIG2Encoder) DecodeBytes(encoded []byte) ([]byte, error) {
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
return encoded, ErrNoJBIG2Decode
}
func (this *JBIG2Encoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) {
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
return streamObj.Stream, ErrNoJBIG2Decode
}
func (this *JBIG2Encoder) EncodeBytes(data []byte) ([]byte, error) {
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
return data, ErrNoJBIG2Decode
}
//
// JPX encoder/decoder (dummy, for now)
//
type JPXEncoder struct{}
func NewJPXEncoder() *JPXEncoder {
return &JPXEncoder{}
}
func (this *JPXEncoder) GetFilterName() string {
return StreamEncodingFilterNameJPX
}
func (this *JPXEncoder) MakeDecodeParams() PdfObject {
return nil
}
// Make a new instance of an encoding dictionary for a stream object.
func (this *JPXEncoder) MakeStreamDict() *PdfObjectDictionary {
return MakeDict()
}
func (this *JPXEncoder) DecodeBytes(encoded []byte) ([]byte, error) {
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
return encoded, ErrNoJPXDecode
}
func (this *JPXEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) {
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
return streamObj.Stream, ErrNoJPXDecode
}
func (this *JPXEncoder) EncodeBytes(data []byte) ([]byte, error) {
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
return data, ErrNoJPXDecode
}
//
// Multi encoder: support serial encoding.
//
@ -1376,10 +1674,11 @@ func newMultiEncoderFromStream(streamObj *PdfObjectStream) (*MultiEncoder, error
arr, isArray := obj.(*PdfObjectArray)
if isArray {
for _, dictObj := range *arr {
dictObj = TraceToDirectObject(dictObj)
if dict, is := dictObj.(*PdfObjectDictionary); is {
decodeParamsArray = append(decodeParamsArray, dict)
} else {
decodeParamsArray = append(decodeParamsArray, nil)
decodeParamsArray = append(decodeParamsArray, MakeDict())
}
}
}
@ -1422,7 +1721,7 @@ func newMultiEncoderFromStream(streamObj *PdfObjectStream) (*MultiEncoder, error
dParams = dict
}
common.Log.Trace("Next name: %s", *name)
common.Log.Trace("Next name: %s, dp: %v, dParams: %v", *name, dp, dParams)
if *name == StreamEncodingFilterNameFlate {
// XXX: need to separate out the DecodeParms..
encoder, err := newFlateEncoderFromStream(streamObj, dParams)

View File

@ -71,6 +71,28 @@ func TestLZWEncoding(t *testing.T) {
}
}
// Test run length encoding.
func TestRunLengthEncoding(t *testing.T) {
rawStream := []byte("this is a dummy text with some \x01\x02\x03 binary data")
encoder := NewRunLengthEncoder()
encoded, err := encoder.EncodeBytes(rawStream)
if err != nil {
t.Errorf("Failed to RunLength encode data: %v", err)
return
}
decoded, err := encoder.DecodeBytes(encoded)
if err != nil {
t.Errorf("Failed to RunLength decode data: %v", err)
return
}
if !compareSlices(decoded, rawStream) {
t.Errorf("Slices not matching. RunLength")
t.Errorf("Decoded (%d): % x", len(encoded), encoded)
t.Errorf("Raw (%d): % x", len(rawStream), rawStream)
return
}
}
// Test ASCII hex encoding.
func TestASCIIHexEncoding(t *testing.T) {
byteData := []byte{0xDE, 0xAD, 0xBE, 0xEF}

View File

@ -17,7 +17,7 @@ func init() {
// when passing a reference to a non-existing object.
func TestFuzzParserTrace1(t *testing.T) {
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(" /Name")
parser.rs, parser.reader, parser.fileSize = makeReaderForText(" /Name")
ref := &PdfObjectReference{ObjectNumber: -1}
obj, err := parser.Trace(ref)
@ -53,7 +53,7 @@ endstream
parser := PdfParser{}
parser.xrefs = make(XrefTable)
parser.objstms = make(ObjectStreams)
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
parser.streamLengthReferenceLookupInProgress = map[int64]bool{}
// Point to the start of the stream (where obj 13 starts).
@ -86,7 +86,7 @@ endstream
parser := PdfParser{}
parser.xrefs = make(XrefTable)
parser.objstms = make(ObjectStreams)
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
parser.streamLengthReferenceLookupInProgress = map[int64]bool{}
// Point to the start of the stream (where obj 13 starts).
@ -109,7 +109,7 @@ endstream
// Test for problem where Encrypt pointing a reference to a non-existing object.
func TestFuzzIsEncryptedFail1(t *testing.T) {
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(" /Name")
parser.rs, parser.reader, parser.fileSize = makeReaderForText(" /Name")
ref := &PdfObjectReference{ObjectNumber: -1}
@ -126,7 +126,7 @@ func TestFuzzIsEncryptedFail1(t *testing.T) {
// Test for trailer Prev entry pointing to an incorrect object type.
func TestFuzzInvalidXrefPrev1(t *testing.T) {
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(`
parser.rs, parser.reader, parser.fileSize = makeReaderForText(`
xref
0 1
0000000000 65535 f

View File

@ -13,14 +13,17 @@ import (
"github.com/unidoc/unidoc/common"
)
func (this *PdfParser) ReadAtLeast(p []byte, n int) (int, error) {
// ReadAtLeast reads at least n bytes into slice p.
// Returns the number of bytes read (should always be == n), and an error on failure.
// TODO (v3): Unexport.
func (parser *PdfParser) ReadAtLeast(p []byte, n int) (int, error) {
remaining := n
start := 0
numRounds := 0
for remaining > 0 {
nRead, err := this.reader.Read(p[start:])
nRead, err := parser.reader.Read(p[start:])
if err != nil {
common.Log.Error("ERROR Failed reading (%d;%d) err=%#v", nRead, numRounds, err)
common.Log.Debug("ERROR Failed reading (%d;%d) %s", nRead, numRounds, err.Error())
return start, errors.New("Failed reading")
}
numRounds++
@ -31,14 +34,16 @@ func (this *PdfParser) ReadAtLeast(p []byte, n int) (int, error) {
}
// Get the current file offset, accounting for buffered position.
func (this *PdfParser) GetFileOffset() int64 {
offset, _ := this.rs.Seek(0, os.SEEK_CUR)
offset -= int64(this.reader.Buffered())
// TODO (v3): Unexport.
func (parser *PdfParser) GetFileOffset() int64 {
offset, _ := parser.rs.Seek(0, os.SEEK_CUR)
offset -= int64(parser.reader.Buffered())
return offset
}
// Seek the file to an offset position.
func (this *PdfParser) SetFileOffset(offset int64) {
this.rs.Seek(offset, os.SEEK_SET)
this.reader = bufio.NewReader(this.rs)
// TODO (v3): Unexport.
func (parser *PdfParser) SetFileOffset(offset int64) {
parser.rs.Seek(offset, os.SEEK_SET)
parser.reader = bufio.NewReader(parser.rs)
}

File diff suppressed because it is too large Load Diff

View File

@ -20,11 +20,11 @@ func init() {
common.SetLogger(common.ConsoleLogger{})
}
func makeReaderForText(txt string) (*bytes.Reader, *bufio.Reader) {
func makeReaderForText(txt string) (*bytes.Reader, *bufio.Reader, int64) {
buf := []byte(txt)
bufReader := bytes.NewReader(buf)
bufferedReader := bufio.NewReader(bufReader)
return bufReader, bufferedReader
return bufReader, bufferedReader, int64(len(txt))
}
func TestNameParsing(t *testing.T) {
@ -47,7 +47,7 @@ func TestNameParsing(t *testing.T) {
for str, name := range namePairs {
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(str)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(str)
o, err := parser.parseName()
if err != nil && err != io.EOF {
t.Errorf("Unable to parse name string, error: %s", err)
@ -59,7 +59,7 @@ func TestNameParsing(t *testing.T) {
// Should fail (require starting with '/')
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(" /Name")
parser.rs, parser.reader, parser.fileSize = makeReaderForText(" /Name")
_, err := parser.parseName()
if err == nil || err == io.EOF {
t.Errorf("Should be invalid name")
@ -91,13 +91,13 @@ func TestStringParsing(t *testing.T) {
}
for _, entry := range testEntries {
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(entry.raw)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(entry.raw)
o, err := parser.parseString()
if err != nil && err != io.EOF {
t.Errorf("Unable to parse string, error: %s", err)
}
if string(o) != entry.expected {
t.Errorf("String Mismatch %s: %q != %q", entry.raw, o, entry.expected)
t.Errorf("String Mismatch %s: \"%s\" != \"%s\"", entry.raw, o, entry.expected)
}
}
}
@ -109,7 +109,7 @@ func TestBinStringParsing(t *testing.T) {
"\x8B\x79\x86\x72\x6A\x8C\xDB)"
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(rawText1)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText1)
o, err := parser.parseString()
if err != nil && err != io.EOF {
t.Errorf("Unable to parse string, error: %s", err)
@ -124,7 +124,7 @@ func TestStringParsing2(t *testing.T) {
rawText := "[(\\227\\224`\\274\\31W\\216\\276\\23\\231\\246U\\33\\317\\6-)(\\210S\\377:\\322\\278A\\200$*/e]\\371|)]"
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
list, err := parser.parseArray()
if err != nil {
t.Errorf("Failed to parse string list (%s)", err)
@ -144,14 +144,14 @@ func TestBoolParsing(t *testing.T) {
for key, expected := range testEntries {
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(key)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(key)
val, err := parser.parseBool()
if err != nil {
t.Errorf("Error parsing bool: %s", err)
return
}
if bool(val) != expected {
t.Errorf("bool not as expected (%t)", val)
t.Errorf("bool not as expected (got %t, expected %t)", bool(val), expected)
return
}
}
@ -161,7 +161,7 @@ func TestNumericParsing1(t *testing.T) {
// 7.3.3
txt1 := "[34.5 -3.62 1 +123.6 4. -.002 0.0]"
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(txt1)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(txt1)
list, err := parser.parseArray()
if err != nil {
t.Errorf("Error parsing array")
@ -188,7 +188,7 @@ func TestNumericParsing1(t *testing.T) {
return
}
if float32(*num) != val {
t.Errorf("Idx %d, value incorrect (%f)", idx)
t.Errorf("Idx %d, value incorrect (%f)", idx, val)
}
}
@ -207,7 +207,7 @@ func TestNumericParsing2(t *testing.T) {
// 7.3.3
txt1 := "[+4.-.002]" // 4.0 and -0.002
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(txt1)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(txt1)
list, err := parser.parseArray()
if err != nil {
t.Errorf("Error parsing array")
@ -230,7 +230,7 @@ func TestNumericParsing2(t *testing.T) {
return
}
if float32(*num) != val {
t.Errorf("Idx %d, value incorrect (%f)", idx)
t.Errorf("Idx %d, value incorrect (%f)", idx, val)
}
}
}
@ -240,7 +240,7 @@ func TestNumericParsing3(t *testing.T) {
// 7.3.3
txt1 := "[+4.-.002+3e-2-2e0]" // 4.0, -0.002, 1e-2, -2.0
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(txt1)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(txt1)
list, err := parser.parseArray()
if err != nil {
t.Errorf("Error parsing array (%s)", err)
@ -265,7 +265,7 @@ func TestNumericParsing3(t *testing.T) {
return
}
if float32(*num) != val {
t.Errorf("Idx %d, value incorrect (%f)", idx)
t.Errorf("Idx %d, value incorrect (%f)", idx, val)
}
}
}
@ -282,7 +282,7 @@ func TestHexStringParsing(t *testing.T) {
func TestDictParsing1(t *testing.T) {
txt1 := "<<\n\t/Name /Game /key/val/data\t[0 1 2 3.14 5]\t\n\n>>"
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(txt1)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(txt1)
dict, err := parser.ParseDict()
if err != nil {
t.Errorf("Error parsing dict")
@ -326,7 +326,7 @@ func TestDictParsing2(t *testing.T) {
">>\n >>"
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
dict, err := parser.ParseDict()
if err != nil {
t.Errorf("Error parsing dict")
@ -364,7 +364,7 @@ func TestDictParsing3(t *testing.T) {
rawText := "<<>>"
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
dict, err := parser.ParseDict()
if err != nil {
t.Errorf("Error parsing dict")
@ -441,7 +441,7 @@ endobj
3 0 obj
`
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
obj, err := parser.ParseIndirectObject()
if err != nil {
@ -475,7 +475,7 @@ endobj`
parser := PdfParser{}
parser.xrefs = make(XrefTable)
parser.objstms = make(ObjectStreams)
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
xrefDict, err := parser.parseXrefStream(nil)
if err != nil {
@ -516,7 +516,7 @@ func TestObjectParse(t *testing.T) {
// Test object detection.
// Invalid object type.
rawText := " \t9 0 false"
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
obj, err := parser.parseObject()
if err != nil {
t.Error("Should ignore tab/space")
@ -525,7 +525,7 @@ func TestObjectParse(t *testing.T) {
// Integer
rawText = "9 0 false"
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
obj, err = parser.parseObject()
if err != nil {
@ -544,7 +544,7 @@ func TestObjectParse(t *testing.T) {
// Reference
rawText = "9 0 R false"
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
obj, err = parser.parseObject()
if err != nil {
t.Errorf("Error parsing object")
@ -562,7 +562,7 @@ func TestObjectParse(t *testing.T) {
// Reference
rawText = "909 0 R false"
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
obj, err = parser.parseObject()
if err != nil {
t.Errorf("Error parsing object")
@ -580,7 +580,7 @@ func TestObjectParse(t *testing.T) {
// Bool
rawText = "false 9 0 R"
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
obj, err = parser.parseObject()
if err != nil {
t.Errorf("Error parsing object")

View File

@ -3,11 +3,6 @@
* file 'LICENSE.md', which is part of this source code package.
*/
// Defines PDF primitive objects as per the standard. Also defines a PdfObject
// interface allowing to universally work with these objects. It allows
// recursive writing of the objects to file as well and stringifying for
// debug purposes.
package core
import (
@ -17,41 +12,63 @@ import (
"github.com/unidoc/unidoc/common"
)
// PDF Primitives implement the PdfObject interface.
// PdfObject is an interface which all primitive PDF objects must implement.
type PdfObject interface {
String() string // Output a string representation of the primitive (for debugging).
DefaultWriteString() string // Output the PDF primitive as expected by the standard.
// Output a string representation of the primitive (for debugging).
String() string
// Output the PDF primitive as written to file as expected by the standard.
DefaultWriteString() string
}
// PdfObjectBool represents the primitive PDF boolean object.
type PdfObjectBool bool
// PdfObjectInteger represents the primitive PDF integer numerical object.
type PdfObjectInteger int64
// PdfObjectFloat represents the primitive PDF floating point numerical object.
type PdfObjectFloat float64
// PdfObjectString represents the primitive PDF string object.
// TODO (v3): Change to a struct and add a flag for hex/plaintext.
type PdfObjectString string
// PdfObjectName represents the primitive PDF name object.
type PdfObjectName string
// PdfObjectArray represents the primitive PDF array object.
type PdfObjectArray []PdfObject
// PdfObjectDictionary represents the primitive PDF dictionary/map object.
type PdfObjectDictionary struct {
dict map[PdfObjectName]PdfObject
keys []PdfObjectName
}
// PdfObjectNull represents the primitive PDF null object.
type PdfObjectNull struct{}
// PdfObjectReference represents the primitive PDF reference object.
type PdfObjectReference struct {
ObjectNumber int64
GenerationNumber int64
}
// PdfIndirectObject represents the primitive PDF indirect object.
type PdfIndirectObject struct {
PdfObjectReference
PdfObject
}
// PdfObjectStream represents the primitive PDF Object stream.
type PdfObjectStream struct {
PdfObjectReference
*PdfObjectDictionary
Stream []byte
}
// Quick functions to make pdf objects form primitive objects.
// MakeDict creates and returns an empty PdfObjectDictionary.
func MakeDict() *PdfObjectDictionary {
d := &PdfObjectDictionary{}
d.dict = map[PdfObjectName]PdfObject{}
@ -59,16 +76,19 @@ func MakeDict() *PdfObjectDictionary {
return d
}
// MakeName creates a PdfObjectName from a string.
func MakeName(s string) *PdfObjectName {
name := PdfObjectName(s)
return &name
}
// MakeInteger creates a PdfObjectInteger from an int64.
func MakeInteger(val int64) *PdfObjectInteger {
num := PdfObjectInteger(val)
return &num
}
// MakeArray creates an PdfObjectArray from a list of PdfObjects.
func MakeArray(objects ...PdfObject) *PdfObjectArray {
array := PdfObjectArray{}
for _, obj := range objects {
@ -77,6 +97,8 @@ func MakeArray(objects ...PdfObject) *PdfObjectArray {
return &array
}
// MakeArrayFromIntegers creates an PdfObjectArray from a slice of ints, where each array element is
// an PdfObjectInteger.
func MakeArrayFromIntegers(vals []int) *PdfObjectArray {
array := PdfObjectArray{}
for _, val := range vals {
@ -85,6 +107,8 @@ func MakeArrayFromIntegers(vals []int) *PdfObjectArray {
return &array
}
// MakeArrayFromIntegers64 creates an PdfObjectArray from a slice of int64s, where each array element
// is an PdfObjectInteger.
func MakeArrayFromIntegers64(vals []int64) *PdfObjectArray {
array := PdfObjectArray{}
for _, val := range vals {
@ -93,6 +117,8 @@ func MakeArrayFromIntegers64(vals []int64) *PdfObjectArray {
return &array
}
// MakeArrayFromFloats creates an PdfObjectArray from a slice of float64s, where each array element is an
// PdfObjectFloat.
func MakeArrayFromFloats(vals []float64) *PdfObjectArray {
array := PdfObjectArray{}
for _, val := range vals {
@ -101,27 +127,33 @@ func MakeArrayFromFloats(vals []float64) *PdfObjectArray {
return &array
}
// MakeFloat creates an PdfObjectFloat from a float64.
func MakeFloat(val float64) *PdfObjectFloat {
num := PdfObjectFloat(val)
return &num
}
// MakeString creates an PdfObjectString from a string.
func MakeString(s string) *PdfObjectString {
str := PdfObjectString(s)
return &str
}
// MakeNull creates an PdfObjectNull.
func MakeNull() *PdfObjectNull {
null := PdfObjectNull{}
return &null
}
// MakeIndirectObject creates an PdfIndirectObject with a specified direct object PdfObject.
func MakeIndirectObject(obj PdfObject) *PdfIndirectObject {
ind := &PdfIndirectObject{}
ind.PdfObject = obj
return ind
}
// MakeStream creates an PdfObjectStream with specified contents and encoding. If encoding is nil, then raw encoding
// will be used (i.e. no encoding applied).
func MakeStream(contents []byte, encoder StreamEncoder) (*PdfObjectStream, error) {
stream := &PdfObjectStream{}
@ -141,44 +173,47 @@ func MakeStream(contents []byte, encoder StreamEncoder) (*PdfObjectStream, error
return stream, nil
}
func (this *PdfObjectBool) String() string {
if *this {
func (bool *PdfObjectBool) String() string {
if *bool {
return "true"
} else {
return "false"
}
}
func (this *PdfObjectBool) DefaultWriteString() string {
if *this {
// DefaultWriteString outputs the object as it is to be written to file.
func (bool *PdfObjectBool) DefaultWriteString() string {
if *bool {
return "true"
} else {
return "false"
}
}
func (this *PdfObjectInteger) String() string {
return fmt.Sprintf("%d", *this)
func (int *PdfObjectInteger) String() string {
return fmt.Sprintf("%d", *int)
}
func (this *PdfObjectInteger) DefaultWriteString() string {
return fmt.Sprintf("%d", *this)
// DefaultWriteString outputs the object as it is to be written to file.
func (int *PdfObjectInteger) DefaultWriteString() string {
return fmt.Sprintf("%d", *int)
}
func (this *PdfObjectFloat) String() string {
return fmt.Sprintf("%f", *this)
func (float *PdfObjectFloat) String() string {
return fmt.Sprintf("%f", *float)
}
func (this *PdfObjectFloat) DefaultWriteString() string {
return fmt.Sprintf("%f", *this)
// DefaultWriteString outputs the object as it is to be written to file.
func (float *PdfObjectFloat) DefaultWriteString() string {
return fmt.Sprintf("%f", *float)
}
func (this *PdfObjectString) String() string {
return fmt.Sprintf("%s", string(*this))
func (str *PdfObjectString) String() string {
return fmt.Sprintf("%s", string(*str))
}
func (this *PdfObjectString) DefaultWriteString() string {
// DefaultWriteString outputs the object as it is to be written to file.
func (str *PdfObjectString) DefaultWriteString() string {
var output bytes.Buffer
escapeSequences := map[byte]string{
@ -193,8 +228,8 @@ func (this *PdfObjectString) DefaultWriteString() string {
}
output.WriteString("(")
for i := 0; i < len(*this); i++ {
char := (*this)[i]
for i := 0; i < len(*str); i++ {
char := (*str)[i]
if escStr, useEsc := escapeSequences[char]; useEsc {
output.WriteString(escStr)
} else {
@ -206,20 +241,21 @@ func (this *PdfObjectString) DefaultWriteString() string {
return output.String()
}
func (this *PdfObjectName) String() string {
return fmt.Sprintf("%s", string(*this))
func (name *PdfObjectName) String() string {
return fmt.Sprintf("%s", string(*name))
}
func (this *PdfObjectName) DefaultWriteString() string {
// DefaultWriteString outputs the object as it is to be written to file.
func (name *PdfObjectName) DefaultWriteString() string {
var output bytes.Buffer
if len(*this) > 127 {
common.Log.Error("Name too long (%s)", *this)
if len(*name) > 127 {
common.Log.Debug("ERROR: Name too long (%s)", *name)
}
output.WriteString("/")
for i := 0; i < len(*this); i++ {
char := (*this)[i]
for i := 0; i < len(*name); i++ {
char := (*name)[i]
if !IsPrintable(char) || char == '#' || IsDelimiter(char) {
output.WriteString(fmt.Sprintf("#%.2x", char))
} else {
@ -230,10 +266,12 @@ func (this *PdfObjectName) DefaultWriteString() string {
return output.String()
}
func (this *PdfObjectArray) ToFloat64Array() ([]float64, error) {
// ToFloat64Array returns a slice of all elements in the array as a float64 slice. An error is returned if the array
// contains non-numeric objects (each element can be either PdfObjectInteger or PdfObjectFloat).
func (array *PdfObjectArray) ToFloat64Array() ([]float64, error) {
vals := []float64{}
for _, obj := range *this {
for _, obj := range *array {
if number, is := obj.(*PdfObjectInteger); is {
vals = append(vals, float64(*number))
} else if number, is := obj.(*PdfObjectFloat); is {
@ -246,10 +284,12 @@ func (this *PdfObjectArray) ToFloat64Array() ([]float64, error) {
return vals, nil
}
func (this *PdfObjectArray) ToIntegerArray() ([]int, error) {
// ToIntegerArray returns a slice of all array elements as an int slice. An error is returned if the array contains
// non-integer objects. Each element can only be PdfObjectInteger.
func (array *PdfObjectArray) ToIntegerArray() ([]int, error) {
vals := []int{}
for _, obj := range *this {
for _, obj := range *array {
if number, is := obj.(*PdfObjectInteger); is {
vals = append(vals, int(*number))
} else {
@ -260,11 +300,11 @@ func (this *PdfObjectArray) ToIntegerArray() ([]int, error) {
return vals, nil
}
func (this *PdfObjectArray) String() string {
func (array *PdfObjectArray) String() string {
outStr := "["
for ind, o := range *this {
for ind, o := range *array {
outStr += o.String()
if ind < (len(*this) - 1) {
if ind < (len(*array) - 1) {
outStr += ", "
}
}
@ -272,11 +312,12 @@ func (this *PdfObjectArray) String() string {
return outStr
}
func (this *PdfObjectArray) DefaultWriteString() string {
// DefaultWriteString outputs the object as it is to be written to file.
func (array *PdfObjectArray) DefaultWriteString() string {
outStr := "["
for ind, o := range *this {
for ind, o := range *array {
outStr += o.DefaultWriteString()
if ind < (len(*this) - 1) {
if ind < (len(*array) - 1) {
outStr += " "
}
}
@ -284,8 +325,9 @@ func (this *PdfObjectArray) DefaultWriteString() string {
return outStr
}
func (this *PdfObjectArray) Append(obj PdfObject) {
*this = append(*this, obj)
// Append adds an PdfObject to the array.
func (array *PdfObjectArray) Append(obj PdfObject) {
*array = append(*array, obj)
}
func getNumberAsFloat(obj PdfObject) (float64, error) {
@ -300,12 +342,12 @@ func getNumberAsFloat(obj PdfObject) (float64, error) {
return 0, fmt.Errorf("Not a number")
}
// For numeric array: Get the array in []float64 slice representation.
// Will return error if not entirely numeric.
func (this *PdfObjectArray) GetAsFloat64Slice() ([]float64, error) {
// GetAsFloat64Slice returns the array as []float64 slice.
// Returns an error if not entirely numeric (only PdfObjectIntegers, PdfObjectFloats).
func (array *PdfObjectArray) GetAsFloat64Slice() ([]float64, error) {
slice := []float64{}
for _, obj := range *this {
for _, obj := range *array {
obj := TraceToDirectObject(obj)
number, err := getNumberAsFloat(obj)
if err != nil {
@ -317,30 +359,31 @@ func (this *PdfObjectArray) GetAsFloat64Slice() ([]float64, error) {
return slice, nil
}
// Merge in key/values from another dictionary. Overwriting if has same keys.
func (this *PdfObjectDictionary) Merge(another *PdfObjectDictionary) {
// Merge merges in key/values from another dictionary. Overwriting if has same keys.
func (d *PdfObjectDictionary) Merge(another *PdfObjectDictionary) {
if another != nil {
for _, key := range another.Keys() {
val := another.Get(key)
this.Set(key, val)
d.Set(key, val)
}
}
}
func (this *PdfObjectDictionary) String() string {
func (d *PdfObjectDictionary) String() string {
outStr := "Dict("
for _, k := range this.keys {
v := this.dict[k]
for _, k := range d.keys {
v := d.dict[k]
outStr += fmt.Sprintf("\"%s\": %s, ", k, v.String())
}
outStr += ")"
return outStr
}
func (this *PdfObjectDictionary) DefaultWriteString() string {
// DefaultWriteString outputs the object as it is to be written to file.
func (d *PdfObjectDictionary) DefaultWriteString() string {
outStr := "<<"
for _, k := range this.keys {
v := this.dict[k]
for _, k := range d.keys {
v := d.dict[k]
common.Log.Trace("Writing k: %s %T %v %v", k, v, k, v)
outStr += k.DefaultWriteString()
outStr += " "
@ -350,6 +393,7 @@ func (this *PdfObjectDictionary) DefaultWriteString() string {
return outStr
}
// Set sets the dictionary's key -> val mapping entry. Overwrites if key already set.
func (d *PdfObjectDictionary) Set(key PdfObjectName, val PdfObject) {
found := false
for _, k := range d.keys {
@ -366,7 +410,7 @@ func (d *PdfObjectDictionary) Set(key PdfObjectName, val PdfObject) {
d.dict[key] = val
}
// Get PdfObject corresponding to the specified key.
// Get returns the PdfObject corresponding to the specified key.
// Returns a nil value if the key is not set.
//
// The design is such that we only return 1 value.
@ -381,12 +425,12 @@ func (d *PdfObjectDictionary) Get(key PdfObjectName) PdfObject {
return val
}
// Get the list of keys.
// Keys returns the list of keys in the dictionary.
func (d *PdfObjectDictionary) Keys() []PdfObjectName {
return d.keys
}
// Remove an element specified by key.
// Remove removes an element specified by key.
func (d *PdfObjectDictionary) Remove(key PdfObjectName) {
idx := -1
for i, k := range d.keys {
@ -403,9 +447,7 @@ func (d *PdfObjectDictionary) Remove(key PdfObjectName) {
}
}
// Check if the value's PdfObject interface, or its containing value is nil. Only set the
// key/value pair if not nil.
//
// SetIfNotNil sets the dictionary's key -> val mapping entry -IF- val is not nil.
// Note that we take care to perform a type switch. Otherwise if we would supply a nil value
// of another type, e.g. (PdfObjectArray*)(nil), then it would not be a PdfObject(nil) and thus
// would get set.
@ -463,50 +505,55 @@ func (d *PdfObjectDictionary) SetIfNotNil(key PdfObjectName, val PdfObject) {
}
}
func (this *PdfObjectReference) String() string {
return fmt.Sprintf("Ref(%d %d)", this.ObjectNumber, this.GenerationNumber)
func (ref *PdfObjectReference) String() string {
return fmt.Sprintf("Ref(%d %d)", ref.ObjectNumber, ref.GenerationNumber)
}
func (this *PdfObjectReference) DefaultWriteString() string {
return fmt.Sprintf("%d %d R", this.ObjectNumber, this.GenerationNumber)
// DefaultWriteString outputs the object as it is to be written to file.
func (ref *PdfObjectReference) DefaultWriteString() string {
return fmt.Sprintf("%d %d R", ref.ObjectNumber, ref.GenerationNumber)
}
func (this *PdfIndirectObject) String() string {
func (ind *PdfIndirectObject) String() string {
// Avoid printing out the object, can cause problems with circular
// references.
return fmt.Sprintf("IObject:%d", (*this).ObjectNumber)
return fmt.Sprintf("IObject:%d", (*ind).ObjectNumber)
}
func (this *PdfIndirectObject) DefaultWriteString() string {
outStr := fmt.Sprintf("%d 0 R", (*this).ObjectNumber)
// DefaultWriteString outputs the object as it is to be written to file.
func (ind *PdfIndirectObject) DefaultWriteString() string {
outStr := fmt.Sprintf("%d 0 R", (*ind).ObjectNumber)
return outStr
}
func (this *PdfObjectStream) String() string {
return fmt.Sprintf("Object stream %d: %s", this.ObjectNumber, this.PdfObjectDictionary)
func (stream *PdfObjectStream) String() string {
return fmt.Sprintf("Object stream %d: %s", stream.ObjectNumber, stream.PdfObjectDictionary)
}
func (this *PdfObjectStream) DefaultWriteString() string {
outStr := fmt.Sprintf("%d 0 R", (*this).ObjectNumber)
// DefaultWriteString outputs the object as it is to be written to file.
func (stream *PdfObjectStream) DefaultWriteString() string {
outStr := fmt.Sprintf("%d 0 R", (*stream).ObjectNumber)
return outStr
}
func (this *PdfObjectNull) String() string {
func (null *PdfObjectNull) String() string {
return "null"
}
func (this *PdfObjectNull) DefaultWriteString() string {
// DefaultWriteString outputs the object as it is to be written to file.
func (null *PdfObjectNull) DefaultWriteString() string {
return "null"
}
// Handy functions to work with primitive objects.
// Traces a pdf object to a direct object. For example contained
// in indirect objects (can be double referenced even).
//
// Note: This function does not trace/resolve references.
// That needs to be done beforehand.
// TraceMaxDepth specifies the maximum recursion depth allowed.
const TraceMaxDepth = 20
// TraceToDirectObject traces a PdfObject to a direct object. For example direct objects contained
// in indirect objects (can be double referenced even).
//
// Note: This function does not trace/resolve references. That needs to be done beforehand.
func TraceToDirectObject(obj PdfObject) PdfObject {
iobj, isIndirectObj := obj.(*PdfIndirectObject)
depth := 0

View File

@ -24,16 +24,16 @@ var repairReXrefTable = regexp.MustCompile(`[\r\n]\s*(xref)\s*[\r\n]`)
// Locates a standard Xref table by looking for the "xref" entry.
// Xref object stream not supported.
func (this *PdfParser) repairLocateXref() (int64, error) {
func (parser *PdfParser) repairLocateXref() (int64, error) {
readBuf := int64(1000)
this.rs.Seek(-readBuf, os.SEEK_CUR)
parser.rs.Seek(-readBuf, os.SEEK_CUR)
curOffset, err := this.rs.Seek(0, os.SEEK_CUR)
curOffset, err := parser.rs.Seek(0, os.SEEK_CUR)
if err != nil {
return 0, err
}
b2 := make([]byte, readBuf)
this.rs.Read(b2)
parser.rs.Read(b2)
results := repairReXrefTable.FindAllStringIndex(string(b2), -1)
if len(results) < 1 {
@ -49,19 +49,19 @@ func (this *PdfParser) repairLocateXref() (int64, error) {
// Renumbers the xref table.
// Useful when the cross reference is pointing to an object with the wrong number.
// Update the table.
func (this *PdfParser) rebuildXrefTable() error {
func (parser *PdfParser) rebuildXrefTable() error {
newXrefs := XrefTable{}
for objNum, xref := range this.xrefs {
obj, _, err := this.lookupByNumberWrapper(objNum, false)
for objNum, xref := range parser.xrefs {
obj, _, err := parser.lookupByNumberWrapper(objNum, false)
if err != nil {
common.Log.Debug("ERROR: Unable to look up object (%s)", err)
common.Log.Debug("ERROR: Xref table completely broken - attempting to repair ")
xrefTable, err := this.repairRebuildXrefsTopDown()
xrefTable, err := parser.repairRebuildXrefsTopDown()
if err != nil {
common.Log.Debug("ERROR: Failed xref rebuild repair (%s)", err)
return err
}
this.xrefs = *xrefTable
parser.xrefs = *xrefTable
common.Log.Debug("Repaired xref table built")
return nil
}
@ -75,9 +75,9 @@ func (this *PdfParser) rebuildXrefTable() error {
newXrefs[int(actObjNum)] = xref
}
this.xrefs = newXrefs
parser.xrefs = newXrefs
common.Log.Debug("New xref table built")
printXrefTable(this.xrefs)
printXrefTable(parser.xrefs)
return nil
}
@ -97,16 +97,16 @@ func parseObjectNumberFromString(str string) (int, int, error) {
// Parse the entire file from top down.
// Goes through the file byte-by-byte looking for "<num> <generation> obj" patterns.
// N.B. This collects the XREF_TABLE_ENTRY data only.
func (this *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) {
if this.repairsAttempted {
func (parser *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) {
if parser.repairsAttempted {
// Avoid multiple repairs (only try once).
return nil, fmt.Errorf("Repair failed")
}
this.repairsAttempted = true
parser.repairsAttempted = true
// Go to beginning, reset reader.
this.rs.Seek(0, os.SEEK_SET)
this.reader = bufio.NewReader(this.rs)
parser.rs.Seek(0, os.SEEK_SET)
parser.reader = bufio.NewReader(parser.rs)
// Keep a running buffer of last bytes.
bufLen := 20
@ -114,7 +114,7 @@ func (this *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) {
xrefTable := XrefTable{}
for {
b, err := this.reader.ReadByte()
b, err := parser.reader.ReadByte()
if err != nil {
if err == io.EOF {
break
@ -157,7 +157,7 @@ func (this *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) {
continue // Probably too long to be a valid object...
}
objOffset := this.GetFileOffset() - int64(bufLen-i)
objOffset := parser.GetFileOffset() - int64(bufLen-i)
objstr := append(last[i+1:], b)
objNum, genNum, err := parseObjectNumberFromString(string(objstr))
@ -185,9 +185,9 @@ func (this *PdfParser) repairRebuildXrefsTopDown() (*XrefTable, error) {
}
// Look for first sign of xref table from end of file.
func (this *PdfParser) repairSeekXrefMarker() error {
func (parser *PdfParser) repairSeekXrefMarker() error {
// Get the file size.
fSize, err := this.rs.Seek(0, os.SEEK_END)
fSize, err := parser.rs.Seek(0, os.SEEK_END)
if err != nil {
return err
}
@ -206,14 +206,14 @@ func (this *PdfParser) repairSeekXrefMarker() error {
}
// Move back enough (as we need to read forward).
_, err := this.rs.Seek(-offset-buflen, os.SEEK_END)
_, err := parser.rs.Seek(-offset-buflen, os.SEEK_END)
if err != nil {
return err
}
// Read the data.
b1 := make([]byte, buflen)
this.rs.Read(b1)
parser.rs.Read(b1)
common.Log.Trace("Looking for xref : \"%s\"", string(b1))
ind := reXrefTableStart.FindAllStringIndex(string(b1), -1)
@ -221,11 +221,11 @@ func (this *PdfParser) repairSeekXrefMarker() error {
// Found it.
lastInd := ind[len(ind)-1]
common.Log.Trace("Ind: % d", ind)
this.rs.Seek(-offset-buflen+int64(lastInd[0]), os.SEEK_END)
this.reader = bufio.NewReader(this.rs)
parser.rs.Seek(-offset-buflen+int64(lastInd[0]), os.SEEK_END)
parser.reader = bufio.NewReader(parser.rs)
// Go past whitespace, finish at 'x'.
for {
bb, err := this.reader.Peek(1)
bb, err := parser.reader.Peek(1)
if err != nil {
return err
}
@ -233,7 +233,7 @@ func (this *PdfParser) repairSeekXrefMarker() error {
if !IsWhiteSpace(bb[0]) {
break
}
this.reader.Discard(1)
parser.reader.Discard(1)
}
return nil
@ -250,17 +250,17 @@ func (this *PdfParser) repairSeekXrefMarker() error {
// Called when Pdf version not found normally. Looks for the PDF version by scanning top-down.
// %PDF-1.7
func (this *PdfParser) seekPdfVersionTopDown() (int, int, error) {
func (parser *PdfParser) seekPdfVersionTopDown() (int, int, error) {
// Go to beginning, reset reader.
this.rs.Seek(0, os.SEEK_SET)
this.reader = bufio.NewReader(this.rs)
parser.rs.Seek(0, os.SEEK_SET)
parser.reader = bufio.NewReader(parser.rs)
// Keep a running buffer of last bytes.
bufLen := 20
last := make([]byte, bufLen)
for {
b, err := this.reader.ReadByte()
b, err := parser.reader.ReadByte()
if err != nil {
if err == io.EOF {
break

View File

@ -11,9 +11,9 @@ import (
"github.com/unidoc/unidoc/common"
)
// Creates the encoder from the stream's dictionary.
// NewEncoderFromStream creates a StreamEncoder based on the stream's dictionary.
func NewEncoderFromStream(streamObj *PdfObjectStream) (StreamEncoder, error) {
filterObj := streamObj.PdfObjectDictionary.Get("Filter")
filterObj := TraceToDirectObject(streamObj.PdfObjectDictionary.Get("Filter"))
if filterObj == nil {
// No filter, return raw data back.
return NewRawEncoder(), nil
@ -61,18 +61,26 @@ func NewEncoderFromStream(streamObj *PdfObjectStream) (StreamEncoder, error) {
return newLZWEncoderFromStream(streamObj, nil)
} else if *method == StreamEncodingFilterNameDCT {
return newDCTEncoderFromStream(streamObj, nil)
} else if *method == StreamEncodingFilterNameRunLength {
return newRunLengthEncoderFromStream(streamObj, nil)
} else if *method == StreamEncodingFilterNameASCIIHex {
return NewASCIIHexEncoder(), nil
} else if *method == StreamEncodingFilterNameASCII85 {
return NewASCII85Encoder(), nil
} else if *method == StreamEncodingFilterNameCCITTFax {
return NewCCITTFaxEncoder(), nil
} else if *method == StreamEncodingFilterNameJBIG2 {
return NewJBIG2Encoder(), nil
} else if *method == StreamEncodingFilterNameJPX {
return NewJPXEncoder(), nil
} else {
common.Log.Debug("ERROR: Unsupported encoding method!")
return nil, fmt.Errorf("Unsupported encoding method (%s)", *method)
}
}
// Decodes the stream.
// Supports FlateDecode, ASCIIHexDecode, LZW.
// DecodeStream decodes the stream data and returns the decoded data.
// An error is returned upon failure.
func DecodeStream(streamObj *PdfObjectStream) ([]byte, error) {
common.Log.Trace("Decode stream")
@ -92,8 +100,7 @@ func DecodeStream(streamObj *PdfObjectStream) ([]byte, error) {
return decoded, nil
}
// Encodes the stream.
// Uses the encoding specified by the object.
// EncodeStream encodes the stream data using the encoded specified by the stream's dictionary.
func EncodeStream(streamObj *PdfObjectStream) error {
common.Log.Trace("Encode stream")

View File

@ -77,7 +77,7 @@ stream
endobj`
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
obj, err := parser.ParseIndirectObject()
if err != nil {
@ -136,7 +136,7 @@ stream
endobj`
parser := PdfParser{}
parser.rs, parser.reader = makeReaderForText(rawText)
parser.rs, parser.reader, parser.fileSize = makeReaderForText(rawText)
obj, err := parser.ParseIndirectObject()
if err != nil {

View File

@ -5,6 +5,8 @@
package core
// IsWhiteSpace checks if byte represents a white space character.
// TODO (v3): Unexport.
func IsWhiteSpace(ch byte) bool {
// Table 1 white-space characters (7.2.2 Character Set)
// spaceCharacters := string([]byte{0x00, 0x09, 0x0A, 0x0C, 0x0D, 0x20})
@ -15,10 +17,14 @@ func IsWhiteSpace(ch byte) bool {
}
}
// IsFloatDigit checks if a character can be a part of a float number string.
// TODO (v3): Unexport.
func IsFloatDigit(c byte) bool {
return ('0' <= c && c <= '9') || c == '.'
}
// IsDecimalDigit checks if the character is a part of a decimal number string.
// TODO (v3): Unexport.
func IsDecimalDigit(c byte) bool {
if c >= '0' && c <= '9' {
return true
@ -27,6 +33,8 @@ func IsDecimalDigit(c byte) bool {
}
}
// IsOctalDigit checks if a character can be part of an octal digit string.
// TODO (v3): Unexport.
func IsOctalDigit(c byte) bool {
if c >= '0' && c <= '7' {
return true
@ -35,8 +43,10 @@ func IsOctalDigit(c byte) bool {
}
}
// IsPrintable checks if a character is printable.
// Regular characters that are outside the range EXCLAMATION MARK(21h)
// (!) to TILDE (7Eh) (~) should be written using the hexadecimal notation.
// TODO (v3): Unexport.
func IsPrintable(char byte) bool {
if char < 0x21 || char > 0x7E {
return false
@ -44,6 +54,8 @@ func IsPrintable(char byte) bool {
return true
}
// IsDelimiter checks if a character represents a delimiter.
// TODO (v3): Unexport.
func IsDelimiter(char byte) bool {
if char == '(' || char == ')' {
return true

View File

@ -6,15 +6,46 @@
package core
import (
"errors"
"fmt"
"sort"
"github.com/unidoc/unidoc/common"
)
// Check slice range to make sure within bounds for accessing:
// slice[a:b] where sliceLen=len(slice).
func checkBounds(sliceLen, a, b int) error {
if a < 0 || a > sliceLen {
return errors.New("Slice index a out of bounds")
}
if b < a {
return errors.New("Invalid slice index b < a")
}
if b > sliceLen {
return errors.New("Slice index b out of bounds")
}
return nil
}
// Inspect analyzes the document object structure.
func (this *PdfParser) Inspect() (map[string]int, error) {
return this.inspect()
func (parser *PdfParser) Inspect() (map[string]int, error) {
return parser.inspect()
}
// GetObjectNums returns a sorted list of object numbers of the PDF objects in the file.
func (parser *PdfParser) GetObjectNums() []int {
objNums := []int{}
for _, x := range parser.xrefs {
objNums = append(objNums, x.objectNumber)
}
// Sort the object numbers to give consistent ordering of PDF objects in output.
// Needed since parser.xrefs is a map.
sort.Ints(objNums)
return objNums
}
func getUniDocVersion() string {
@ -26,7 +57,7 @@ func getUniDocVersion() string {
* Go through all objects in the cross ref table and detect the types.
* Mostly for debugging purposes and inspecting odd PDF files.
*/
func (this *PdfParser) inspect() (map[string]int, error) {
func (parser *PdfParser) inspect() (map[string]int, error) {
common.Log.Trace("--------INSPECT ----------")
common.Log.Trace("Xref table:")
@ -35,21 +66,21 @@ func (this *PdfParser) inspect() (map[string]int, error) {
failedCount := 0
keys := []int{}
for k, _ := range this.xrefs {
for k := range parser.xrefs {
keys = append(keys, k)
}
sort.Ints(keys)
i := 0
for _, k := range keys {
xref := this.xrefs[k]
xref := parser.xrefs[k]
if xref.objectNumber == 0 {
continue
}
objCount++
common.Log.Trace("==========")
common.Log.Trace("Looking up object number: %d", xref.objectNumber)
o, err := this.LookupByNumber(xref.objectNumber)
o, err := parser.LookupByNumber(xref.objectNumber)
if err != nil {
common.Log.Trace("ERROR: Fail to lookup obj %d (%s)", xref.objectNumber, err)
failedCount++
@ -129,8 +160,8 @@ func (this *PdfParser) inspect() (map[string]int, error) {
}
common.Log.Trace("=======")
if len(this.xrefs) < 1 {
common.Log.Error("This document is invalid (xref table missing!)")
if len(parser.xrefs) < 1 {
common.Log.Debug("ERROR: This document is invalid (xref table missing!)")
return nil, fmt.Errorf("Invalid document (xref table missing)")
}

View File

@ -15,7 +15,7 @@ import (
"github.com/unidoc/unidoc/pdf/model"
)
// A block can contain a portion of PDF Page contents. It has a width and a position and can
// Block contains a portion of PDF Page contents. It has a width and a position and can
// be placed anywhere on a Page. It can even contain a whole Page, and is used in the creator
// where each Drawable object can output one or more blocks, each representing content for separate pages
// (typically needed when Page breaks occur).
@ -41,7 +41,7 @@ type Block struct {
margins margins
}
// Create a new block with specified width and height.
// NewBlock creates a new Block with specified width and height.
func NewBlock(width float64, height float64) *Block {
b := &Block{}
b.contents = &contentstream.ContentStreamOperations{}
@ -51,8 +51,8 @@ func NewBlock(width float64, height float64) *Block {
return b
}
// Create a block from a PDF Page. Useful for loading template pages as blocks from a PDF document and additional
// content with the creator.
// NewBlockFromPage creates a Block from a PDF Page. Useful for loading template pages as blocks from a PDF document
// and additional content with the creator.
func NewBlockFromPage(page *model.PdfPage) (*Block, error) {
b := &Block{}
@ -91,7 +91,7 @@ func NewBlockFromPage(page *model.PdfPage) (*Block, error) {
return b, nil
}
// Set the rotation angle in degrees.
// SetAngle sets the rotation angle in degrees.
func (blk *Block) SetAngle(angleDeg float64) {
blk.angle = angleDeg
}
@ -112,7 +112,8 @@ func (blk *Block) duplicate() *Block {
return dup
}
// Draws the block contents on a template Page block.
// GeneratePageBlocks draws the block contents on a template Page block.
// Implements the Drawable interface.
func (blk *Block) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
blocks := []*Block{}
@ -156,12 +157,12 @@ func (blk *Block) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, er
return blocks, ctx, nil
}
// Get block height.
// Height returns the Block's height.
func (blk *Block) Height() float64 {
return blk.height
}
// Get block width.
// Width returns the Block's width.
func (blk *Block) Width() float64 {
return blk.width
}
@ -189,7 +190,7 @@ func (blk *Block) addContentsByString(contents string) error {
return nil
}
// Set block Margins.
// SetMargins sets the Block's left, right, top, bottom, margins.
func (blk *Block) SetMargins(left, right, top, bottom float64) {
blk.margins.left = left
blk.margins.right = right
@ -197,12 +198,12 @@ func (blk *Block) SetMargins(left, right, top, bottom float64) {
blk.margins.bottom = bottom
}
// Return block Margins: left, right, top, bottom Margins.
// GetMargins returns the Block's margins: left, right, top, bottom.
func (blk *Block) GetMargins() (float64, float64, float64, float64) {
return blk.margins.left, blk.margins.right, blk.margins.top, blk.margins.bottom
}
// Set block positioning to absolute and set the absolute position coordinates as specified.
// SetPos sets the Block's positioning to absolute mode with the specified coordinates.
func (blk *Block) SetPos(x, y float64) {
blk.positioning = positionAbsolute
blk.xPos = x
@ -222,13 +223,13 @@ func (blk *Block) Scale(sx, sy float64) {
blk.height *= sy
}
// Scale to a specified width, maintaining aspect ratio.
// ScaleToWidth scales the Block to a specified width, maintaining the same aspect ratio.
func (blk *Block) ScaleToWidth(w float64) {
ratio := w / blk.width
blk.Scale(ratio, ratio)
}
// Scale to a specified height, maintaining aspect ratio.
// ScaleToHeight scales the Block to a specified height, maintaining the same aspect ratio.
func (blk *Block) ScaleToHeight(h float64) {
ratio := h / blk.height
blk.Scale(ratio, ratio)
@ -308,7 +309,7 @@ func (blk *Block) Draw(d Drawable) error {
return nil
}
// Draw with context.
// DrawWithContext draws the Block using the specified drawing context.
func (blk *Block) DrawWithContext(d Drawable, ctx DrawContext) error {
blocks, _, err := d.GeneratePageBlocks(ctx)
if err != nil {

View File

@ -13,10 +13,12 @@ import (
"github.com/unidoc/unidoc/pdf/model/fonts"
)
// Chapter is used to arrange multiple drawables (paragraphs, images, etc) into a single section. The concept is
// the same as a book or a report chapter.
type Chapter struct {
number int
title string
heading *paragraph
heading *Paragraph
subchapters int
@ -41,6 +43,7 @@ type Chapter struct {
toc *TableOfContents
}
// NewChapter creates a new chapter with the specified title as the heading.
func (c *Creator) NewChapter(title string) *Chapter {
chap := &Chapter{}
@ -65,7 +68,7 @@ func (c *Creator) NewChapter(title string) *Chapter {
return chap
}
// Set flag to indicate whether or not to show chapter numbers as part of title.
// SetShowNumbering sets a flag to indicate whether or not to show chapter numbers as part of title.
func (chap *Chapter) SetShowNumbering(show bool) {
if show {
heading := fmt.Sprintf("%d. %s", chap.number, chap.title)
@ -77,26 +80,18 @@ func (chap *Chapter) SetShowNumbering(show bool) {
chap.showNumbering = show
}
// Set flag to indicate whether or not to include in tOC.
// SetIncludeInTOC sets a flag to indicate whether or not to include in tOC.
func (chap *Chapter) SetIncludeInTOC(includeInTOC bool) {
chap.includeInTOC = includeInTOC
}
// Get access to the heading paragraph to address style etc.
func (chap *Chapter) GetHeading() *paragraph {
// GetHeading returns the chapter heading paragraph. Used to give access to address style: font, sizing etc.
func (chap *Chapter) GetHeading() *Paragraph {
return chap.heading
}
/*
// Set absolute coordinates.
func (chap *Chapter) SetPos(x, y float64) {
chap.positioning = positionAbsolute
chap.xPos = x
chap.yPos = y
}
*/
// Set chapter Margins. Typically not needed as the Page Margins are used.
// SetMargins sets the Chapter margins: left, right, top, bottom.
// Typically not needed as the creator's page margins are used.
func (chap *Chapter) SetMargins(left, right, top, bottom float64) {
chap.margins.left = left
chap.margins.right = right
@ -104,12 +99,12 @@ func (chap *Chapter) SetMargins(left, right, top, bottom float64) {
chap.margins.bottom = bottom
}
// Get chapter Margins: left, right, top, bottom.
// GetMargins returns the Chapter's margin: left, right, top, bottom.
func (chap *Chapter) GetMargins() (float64, float64, float64, float64) {
return chap.margins.left, chap.margins.right, chap.margins.top, chap.margins.bottom
}
// Add a new drawable to the chapter.
// Add adds a new Drawable to the chapter.
func (chap *Chapter) Add(d Drawable) error {
if Drawable(chap) == d {
common.Log.Debug("ERROR: Cannot add itself")
@ -120,7 +115,7 @@ func (chap *Chapter) Add(d Drawable) error {
case *Chapter:
common.Log.Debug("Error: Cannot add chapter to a chapter")
return errors.New("Type check error")
case *paragraph, *image, *Block, *subchapter, *Table:
case *Paragraph, *Image, *Block, *Subchapter, *Table:
chap.contents = append(chap.contents, d)
default:
common.Log.Debug("Unsupported: %T", d)
@ -130,7 +125,7 @@ func (chap *Chapter) Add(d Drawable) error {
return nil
}
// Generate the Page blocks. Multiple blocks are generated if the contents wrap over
// GeneratePageBlocks generate the Page blocks. Multiple blocks are generated if the contents wrap over
// multiple pages.
func (chap *Chapter) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
origCtx := ctx

View File

@ -13,8 +13,8 @@ import (
"github.com/unidoc/unidoc/common"
)
// Represents colors in the PDF creator.
type color interface {
// Color interface represents colors in the PDF creator.
type Color interface {
ToRGB() (float64, float64, float64)
}
@ -38,10 +38,10 @@ var (
ColorYellow = ColorRGBFromArithmetic(1, 1, 0)
)
// Convert color hex code to rgb color for using with creator.
// ColorRGBFromHex converts color hex code to rgb color for using with creator.
// NOTE: If there is a problem interpreting the string, then will use black color and log a debug message.
// Example hex code: #ffffff -> (1,1,1) white.
func ColorRGBFromHex(hexStr string) color {
func ColorRGBFromHex(hexStr string) Color {
color := rgbColor{}
if (len(hexStr) != 4 && len(hexStr) != 7) || hexStr[0] != '#' {
common.Log.Debug("Invalid hex code: %s", hexStr)
@ -90,10 +90,10 @@ func ColorRGBFromHex(hexStr string) color {
return color
}
// Color from 8bit (0-255) r,g,b values.
// ColorRGBFrom8bit creates a Color from 8bit (0-255) r,g,b values.
// Example:
// red := ColorRGBFrom8Bit(255, 0, 0)
func ColorRGBFrom8bit(r, g, b byte) color {
func ColorRGBFrom8bit(r, g, b byte) Color {
color := rgbColor{}
color.r = float64(r) / 255.0
color.g = float64(g) / 255.0
@ -101,10 +101,10 @@ func ColorRGBFrom8bit(r, g, b byte) color {
return color
}
// Color from arithmetic (0-1.0) color values.
// ColorRGBFromArithmetic creates a Color from arithmetic (0-1.0) color values.
// Example:
// green := ColorRGBFromArithmetic(0, 1.0, 0)
func ColorRGBFromArithmetic(r, g, b float64) color {
func ColorRGBFromArithmetic(r, g, b float64) Color {
// Ensure is in the range 0-1:
r = math.Max(math.Min(r, 1.0), 0.0)
g = math.Max(math.Min(g, 1.0), 0.0)

View File

@ -5,10 +5,13 @@
package creator
// PageSize represents the page size as a 2 element array representing the width and height in PDF document units (points).
type PageSize [2]float64
// Default PDF resolution (points/inch, points/mm).
var PPI float64 = 72 // Points per inch. (Default resolution).
// PPI specifies the default PDF resolution in points/inch.
var PPI float64 = 72 // Points per inch. (Default resolution).
// PPMM specifies the default PDF resolution in points/mm.
var PPMM float64 = 72 * 1.0 / 25.4 // Points per mm. (Default resolution).
//
@ -25,6 +28,11 @@ var (
// TextAlignment options for paragraph.
type TextAlignment int
// The options supported for text alignment are:
// left - TextAlignmentLeft
// right - TextAlignmentRight
// center - TextAlignmentCenter
// justify - TextAlignmentJustify
const (
TextAlignmentLeft TextAlignment = iota
TextAlignmentRight

View File

@ -14,10 +14,8 @@ import (
"github.com/unidoc/unidoc/pdf/model"
)
//
// The content creator is a wrapper around functionality for creating PDF reports and/or adding new
// content onto imported PDF pages.
//
// Creator is a wrapper around functionality for creating PDF reports and/or adding new
// content onto imported PDF pages, etc.
type Creator struct {
pages []*model.PdfPage
activePage *model.PdfPage
@ -33,29 +31,34 @@ type Creator struct {
// Keep track of number of chapters for indexing.
chapters int
// Hooks.
genFrontPageFunc func(args FrontpageFunctionArgs)
genTableOfContentFunc func(toc *TableOfContents) (*Chapter, error)
drawHeaderFunc func(header *Block, args HeaderFunctionArgs)
drawFooterFunc func(footer *Block, args FooterFunctionArgs)
pdfWriterAccessFunc func(writer *model.PdfWriter) error
finalized bool
toc *TableOfContents
}
// Input arguments to a front page drawing function.
// FrontpageFunctionArgs holds the input arguments to a front page drawing function.
// It is designed as a struct, so additional parameters can be added in the future with backwards compatibility.
type FrontpageFunctionArgs struct {
PageNum int
TotalPages int
}
// Input arguments to a header drawing function.
// HeaderFunctionArgs holds the input arguments to a header drawing function.
// It is designed as a struct, so additional parameters can be added in the future with backwards compatibility.
type HeaderFunctionArgs struct {
PageNum int
TotalPages int
}
// Input arguments to a footer drawing function.
// FooterFunctionArgs holds the input arguments to a footer drawing function.
// It is designed as a struct, so additional parameters can be added in the future with backwards compatibility.
type FooterFunctionArgs struct {
PageNum int
TotalPages int
@ -69,7 +72,7 @@ type margins struct {
bottom float64
}
// Create a new instance of the PDF creator.
// New creates a new instance of the PDF Creator.
func New() *Creator {
c := &Creator{}
c.pages = []*model.PdfPage{}
@ -86,7 +89,7 @@ func New() *Creator {
return c
}
// Set the page margins: left, right, top, bottom.
// SetPageMargins sets the page margins: left, right, top, bottom.
// The default page margins are 10% of document width.
func (c *Creator) SetPageMargins(left, right, top, bottom float64) {
c.pageMargins.left = left
@ -95,12 +98,12 @@ func (c *Creator) SetPageMargins(left, right, top, bottom float64) {
c.pageMargins.bottom = bottom
}
// Returns the current Page width.
// Width returns the current page width.
func (c *Creator) Width() float64 {
return c.pageWidth
}
// Returns the current Page height.
// Height returns the current page height.
func (c *Creator) Height() float64 {
return c.pageHeight
}
@ -115,12 +118,11 @@ func (c *Creator) getActivePage() *model.PdfPage {
return nil
}
return c.pages[len(c.pages)-1]
} else {
return c.activePage
}
return c.activePage
}
// Set a new Page size. Pages that are added after this will be created with this Page size.
// SetPageSize sets the Creator's page size. Pages that are added after this will be created with this Page size.
// Does not affect pages already created.
//
// Common page sizes are defined as constants.
@ -151,22 +153,22 @@ func (c *Creator) SetPageSize(size PageSize) {
c.pageMargins.bottom = m
}
// Set a function to draw a header on created output pages.
// DrawHeader sets a function to draw a header on created output pages.
func (c *Creator) DrawHeader(drawHeaderFunc func(header *Block, args HeaderFunctionArgs)) {
c.drawHeaderFunc = drawHeaderFunc
}
// Set a function to draw a footer on created output pages.
// DrawFooter sets a function to draw a footer on created output pages.
func (c *Creator) DrawFooter(drawFooterFunc func(footer *Block, args FooterFunctionArgs)) {
c.drawFooterFunc = drawFooterFunc
}
// Set a function to generate a front Page.
// CreateFrontPage sets a function to generate a front Page.
func (c *Creator) CreateFrontPage(genFrontPageFunc func(args FrontpageFunctionArgs)) {
c.genFrontPageFunc = genFrontPageFunc
}
// Seta function to generate table of contents.
// CreateTableOfContents sets a function to generate table of contents.
func (c *Creator) CreateTableOfContents(genTOCFunc func(toc *TableOfContents) (*Chapter, error)) {
c.genTableOfContentFunc = genTOCFunc
}
@ -201,13 +203,14 @@ func (c *Creator) initContext() {
c.context.Margins = c.pageMargins
}
// Adds a new Page to the creator and sets as the active Page.
// NewPage adds a new Page to the Creator and sets as the active Page.
func (c *Creator) NewPage() {
page := c.newPage()
c.pages = append(c.pages, page)
c.context.Page++
}
// AddPage adds the specified page to the creator.
func (c *Creator) AddPage(page *model.PdfPage) error {
mbox, err := page.GetMediaBox()
if err != nil {
@ -226,8 +229,8 @@ func (c *Creator) AddPage(page *model.PdfPage) error {
return nil
}
// Rotate the current active page by angle degrees.
// NOTE: angleDeg must be a multiple of 90.
// RotateDeg rotates the current active page by angle degrees. An error is returned on failure, which can be
// if there is no currently active page, or the angleDeg is not a multiple of 90 degrees.
func (c *Creator) RotateDeg(angleDeg int64) error {
page := c.getActivePage()
if page == nil {
@ -250,7 +253,7 @@ func (c *Creator) RotateDeg(angleDeg int64) error {
return nil
}
// Get current context.
// Context returns the current drawing context.
func (c *Creator) Context() DrawContext {
return c.context
}
@ -282,7 +285,7 @@ func (c *Creator) finalize() error {
genpages += len(blocks)
// Update the table of content Page numbers, accounting for front Page and TOC.
for idx, _ := range c.toc.entries {
for idx := range c.toc.entries {
c.toc.entries[idx].PageNumber += genpages
}
@ -384,33 +387,33 @@ func (c *Creator) finalize() error {
return nil
}
// Move absolute position to x, y.
// MoveTo moves the drawing context to absolute coordinates (x, y).
func (c *Creator) MoveTo(x, y float64) {
c.context.X = x
c.context.Y = y
}
// Move draw context to absolute position x.
// MoveX moves the drawing context to absolute position x.
func (c *Creator) MoveX(x float64) {
c.context.X = x
}
// Move draw context to absolute position y.
// MoveY moves the drawing context to absolute position y.
func (c *Creator) MoveY(y float64) {
c.context.Y = y
}
// Move draw context right by relative position dx (negative goes left).
// MoveRight moves the drawing context right by relative displacement dx (negative goes left).
func (c *Creator) MoveRight(dx float64) {
c.context.X += dx
}
// Move draw context down by relative position dy (negative goes up).
// MoveDown moves the drawing context down by relative displacement dy (negative goes up).
func (c *Creator) MoveDown(dy float64) {
c.context.Y += dy
}
// Draw the drawable widget to the document. This can span over 1 or more pages. Additional pages are added if
// Draw draws the Drawable widget to the document. This can span over 1 or more pages. Additional pages are added if
// the contents go over the current Page.
func (c *Creator) Draw(d Drawable) error {
if c.getActivePage() == nil {
@ -451,6 +454,15 @@ func (c *Creator) Write(ws io.WriteSeeker) error {
pdfWriter := model.NewPdfWriter()
// Pdf Writer access hook. Can be used to encrypt, etc. via the PdfWriter instance.
if c.pdfWriterAccessFunc != nil {
err := c.pdfWriterAccessFunc(&pdfWriter)
if err != nil {
common.Log.Debug("Failure: %v", err)
return err
}
}
for _, page := range c.pages {
err := pdfWriter.AddPage(page)
if err != nil {
@ -467,7 +479,24 @@ func (c *Creator) Write(ws io.WriteSeeker) error {
return nil
}
// Write output of creator to file.
// SetPdfWriterAccessFunc sets a PdfWriter access function/hook.
// Exposes the PdfWriter just prior to writing the PDF. Can be used to encrypt the output PDF, etc.
//
// Example of encrypting with a user/owner password "password"
// Prior to calling c.WriteFile():
//
// c.SetPdfWriterAccessFunc(func(w *model.PdfWriter) error {
// userPass := []byte("password")
// ownerPass := []byte("password")
// err := w.Encrypt(userPass, ownerPass, nil)
// return err
// })
//
func (c *Creator) SetPdfWriterAccessFunc(pdfWriterAccessFunc func(writer *model.PdfWriter) error) {
c.pdfWriterAccessFunc = pdfWriterAccessFunc
}
// WriteToFile writes the Creator output to file specified by path.
func (c *Creator) WriteToFile(outputPath string) error {
fWrite, err := os.Create(outputPath)
if err != nil {

View File

@ -34,6 +34,8 @@ const testPdfLoremIpsumFile = "../../testfiles/lorem.pdf"
const testPdfTemplatesFile1 = "../../testfiles/templates1.pdf"
const testImageFile1 = "../../testfiles/logo.png"
const testImageFile2 = "../../testfiles/signature.png"
const testRobotoRegularTTFFile = "../../testfiles/roboto/Roboto-Regular.ttf"
const testRobotoBoldTTFFile = "../../testfiles/roboto/Roboto-Bold.ttf"
func TestTemplate1(t *testing.T) {
creator := New()
@ -437,23 +439,23 @@ func TestParagraphWrapping2(t *testing.T) {
}
}
// Test writing with various TTF fonts. Assumes MacOS system, where fonts are stored under /Library/Fonts.
// Test writing with TTF fonts.
func TestParagraphFonts(t *testing.T) {
creator := New()
verdana, err := model.NewPdfFontFromTTFFile("/Library/Fonts/Verdana.ttf")
roboto, err := model.NewPdfFontFromTTFFile(testRobotoRegularTTFFile)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
arialBold, err := model.NewPdfFontFromTTFFile("/Library/Fonts/Arial Bold.ttf")
robotoBold, err := model.NewPdfFontFromTTFFile(testRobotoBoldTTFFile)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
fonts := []fonts.Font{verdana, arialBold, fonts.NewFontHelvetica(), verdana, arialBold, fonts.NewFontHelvetica()}
fonts := []fonts.Font{roboto, robotoBold, fonts.NewFontHelvetica(), roboto, robotoBold, fonts.NewFontHelvetica()}
for _, font := range fonts {
p := NewParagraph("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt" +
"ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " +
@ -1263,3 +1265,36 @@ func TestQRCodeOnTemplate(t *testing.T) {
// Write the example to file.
creator.WriteToFile("/tmp/4_barcode_on_tpl.pdf")
}
// Test adding encryption to output.
func TestEncrypting1(t *testing.T) {
c := New()
ch1 := c.NewChapter("Introduction")
p := NewParagraph("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " +
"ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " +
"aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore " +
"eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " +
"mollit anim id est laborum.")
p.SetMargins(0, 0, 10, 0)
for j := 0; j < 55; j++ {
ch1.Add(p) // Can add any drawable..
}
c.Draw(ch1)
c.SetPdfWriterAccessFunc(func(w *model.PdfWriter) error {
userPass := []byte("password")
ownerPass := []byte("password")
err := w.Encrypt(userPass, ownerPass, nil)
return err
})
err := c.WriteToFile("/tmp/6_chapters_encrypted_password.pdf")
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
}

View File

@ -4,7 +4,7 @@
*/
//
// The creator is used for quickly generating pages and content with a simple interface.
// Package creator is used for quickly generating pages and content with a simple interface.
// It is built on top of the model package to provide access to the most common
// operations such as creating text and image reports and manipulating existing pages.
//

View File

@ -5,7 +5,7 @@
package creator
// All widgets that can be used to draw with the creator need to implement the Drawable interface.
// Drawable is a widget that can be used to draw with the Creator.
type Drawable interface {
// Draw onto blocks representing Page contents. As the content can wrap over many pages, multiple
// templates are returned, one per Page. The function also takes a draw context containing information
@ -13,15 +13,16 @@ type Drawable interface {
GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error)
}
// A vector drawable is a Drawable with a specified width and height.
// VectorDrawable is a Drawable with a specified width and height.
type VectorDrawable interface {
Drawable
Width() float64
Height() float64
}
// Drawing context. Continuously used when drawing the page contents. Keeps track of current X, Y position,
// available height as well as other page parameters such as margins and dimensions.
// DrawContext defines the drawing context. The DrawContext is continuously used and updated when drawing the page
// contents in relative mode. Keeps track of current X, Y position, available height as well as other page parameters
// such as margins and dimensions.
type DrawContext struct {
// Current page number.
Page int

View File

@ -10,12 +10,10 @@ import (
"github.com/unidoc/unidoc/pdf/model"
)
//
// Defines an ellipse with a center at (xc,yc) and a specified width and height. The ellipse can have a colored
// Ellipse defines an ellipse with a center at (xc,yc) and a specified width and height. The ellipse can have a colored
// fill and/or border with a specified width.
// Implements the Drawable interface and can be drawn on PDF using the Creator.
//
type ellipse struct {
type Ellipse struct {
xc float64
yc float64
width float64
@ -25,9 +23,9 @@ type ellipse struct {
borderWidth float64
}
// Generates a new ellipse centered at (xc,yc) with a width and height specified.
func NewEllipse(xc, yc, width, height float64) *ellipse {
ell := &ellipse{}
// NewEllipse creates a new ellipse centered at (xc,yc) with a width and height specified.
func NewEllipse(xc, yc, width, height float64) *Ellipse {
ell := &Ellipse{}
ell.xc = xc
ell.yc = yc
@ -40,28 +38,28 @@ func NewEllipse(xc, yc, width, height float64) *ellipse {
return ell
}
// Get the coordinates of the ellipse center (xc,yc).
func (ell *ellipse) GetCoords() (float64, float64) {
// GetCoords returns the coordinates of the Ellipse's center (xc,yc).
func (ell *Ellipse) GetCoords() (float64, float64) {
return ell.xc, ell.yc
}
// Set border width.
func (ell *ellipse) SetBorderWidth(bw float64) {
// SetBorderWidth sets the border width.
func (ell *Ellipse) SetBorderWidth(bw float64) {
ell.borderWidth = bw
}
// Set border color.
func (ell *ellipse) SetBorderColor(col color) {
// SetBorderColor sets the border color.
func (ell *Ellipse) SetBorderColor(col Color) {
ell.borderColor = model.NewPdfColorDeviceRGB(col.ToRGB())
}
// Set fill color.
func (ell *ellipse) SetFillColor(col color) {
// SetFillColor sets the fill color.
func (ell *Ellipse) SetFillColor(col Color) {
ell.fillColor = model.NewPdfColorDeviceRGB(col.ToRGB())
}
// Draws the rectangle on a new block representing the page.
func (ell *ellipse) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
// GeneratePageBlocks draws the rectangle on a new block representing the page.
func (ell *Ellipse) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
block := NewBlock(ctx.PageWidth, ctx.PageHeight)
drawell := draw.Circle{

View File

@ -18,8 +18,8 @@ import (
"github.com/unidoc/unidoc/pdf/model"
)
// The image type is used to draw an image onto PDF.
type image struct {
// The Image type is used to draw an image onto PDF.
type Image struct {
xobj *model.XObjectImage
// Rotation angle.
@ -48,9 +48,9 @@ type image struct {
rotOriginX, rotOriginY float64
}
// Create a new image from a unidoc image model.
func NewImage(img *model.Image) (*image, error) {
image := &image{}
// NewImage create a new image from a unidoc image (model.Image).
func NewImage(img *model.Image) (*Image, error) {
image := &Image{}
// Create the XObject image.
ximg, err := model.NewXObjectImageFromImage(img, nil, core.NewFlateEncoder())
@ -74,8 +74,8 @@ func NewImage(img *model.Image) (*image, error) {
return image, nil
}
// Create an image from image data.
func NewImageFromData(data []byte) (*image, error) {
// NewImageFromData creates an Image from image data.
func NewImageFromData(data []byte) (*Image, error) {
imgReader := bytes.NewReader(data)
// Load the image with default handler.
@ -88,8 +88,8 @@ func NewImageFromData(data []byte) (*image, error) {
return NewImage(img)
}
// Create image from a file.
func NewImageFromFile(path string) (*image, error) {
// NewImageFromFile creates an Image from a file.
func NewImageFromFile(path string) (*Image, error) {
imgData, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
@ -103,8 +103,8 @@ func NewImageFromFile(path string) (*image, error) {
return img, nil
}
// Create image from a go image.Image datastructure.
func NewImageFromGoImage(goimg goimage.Image) (*image, error) {
// NewImageFromGoImage creates an Image from a go image.Image datastructure.
func NewImageFromGoImage(goimg goimage.Image) (*Image, error) {
img, err := model.ImageHandling.NewImageFromGoImage(goimg)
if err != nil {
return nil, err
@ -113,36 +113,36 @@ func NewImageFromGoImage(goimg goimage.Image) (*image, error) {
return NewImage(img)
}
// Get image document height.
func (img *image) Height() float64 {
// Height returns Image's document height.
func (img *Image) Height() float64 {
return img.height
}
// Get image document width.
func (img *image) Width() float64 {
// Width returns Image's document width.
func (img *Image) Width() float64 {
return img.width
}
// Set opacity
func (img *image) SetOpacity(opacity float64) {
// SetOpacity sets opacity for Image.
func (img *Image) SetOpacity(opacity float64) {
img.opacity = opacity
}
// Set the image Margins.
func (img *image) SetMargins(left, right, top, bottom float64) {
// SetMargins sets the margins for the Image (in relative mode): left, right, top, bottom.
func (img *Image) SetMargins(left, right, top, bottom float64) {
img.margins.left = left
img.margins.right = right
img.margins.top = top
img.margins.bottom = bottom
}
// Get image Margins: left, right, top, bottom.
func (img *image) GetMargins() (float64, float64, float64, float64) {
// GetMargins returns the Image's margins: left, right, top, bottom.
func (img *Image) GetMargins() (float64, float64, float64, float64) {
return img.margins.left, img.margins.right, img.margins.top, img.margins.bottom
}
// Generate the Page blocks.
func (img *image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
// GeneratePageBlocks generate the Page blocks. Draws the Image on a block, implementing the Drawable interface.
func (img *Image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
blocks := []*Block{}
origCtx := ctx
@ -163,6 +163,11 @@ func (img *image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, er
newContext.Height = ctx.PageHeight - ctx.Margins.top - ctx.Margins.bottom - img.margins.bottom
newContext.Width = ctx.PageWidth - ctx.Margins.left - ctx.Margins.right - img.margins.left - img.margins.right
ctx = newContext
} else {
ctx.Y += img.margins.top
ctx.Height -= img.margins.top + img.margins.bottom
ctx.X += img.margins.left
ctx.Width -= img.margins.left + img.margins.right
}
} else {
// Absolute.
@ -170,7 +175,7 @@ func (img *image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, er
ctx.Y = img.yPos
}
// Place the image on the template at position (x,y) based on the ctx.
// Place the Image on the template at position (x,y) based on the ctx.
ctx, err := drawImageOnBlock(blk, img, ctx)
if err != nil {
return nil, ctx, err
@ -183,56 +188,58 @@ func (img *image) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, er
ctx = origCtx
} else {
// XXX/TODO: Use projected height.
ctx.Y += img.height
ctx.Y += img.margins.bottom
ctx.Height -= img.margins.bottom
}
return blocks, ctx, nil
}
// Set absolute position. Changes object positioning to absolute.
func (img *image) SetPos(x, y float64) {
// SetPos sets the absolute position. Changes object positioning to absolute.
func (img *Image) SetPos(x, y float64) {
img.positioning = positionAbsolute
img.xPos = x
img.yPos = y
}
// Scale image by a constant factor, both width and height.
func (img *image) Scale(xFactor, yFactor float64) {
// Scale scales Image by a constant factor, both width and height.
func (img *Image) Scale(xFactor, yFactor float64) {
img.width = xFactor * img.width
img.height = yFactor * img.height
}
// Scale image to a specified width w, maintaining the aspect ratio.
func (img *image) ScaleToWidth(w float64) {
// ScaleToWidth scale Image to a specified width w, maintaining the aspect ratio.
func (img *Image) ScaleToWidth(w float64) {
ratio := img.height / img.width
img.width = w
img.height = w * ratio
}
// Scale image to a specified height h, maintaining the aspect ratio.
func (img *image) ScaleToHeight(h float64) {
// ScaleToHeight scale Image to a specified height h, maintaining the aspect ratio.
func (img *Image) ScaleToHeight(h float64) {
ratio := img.width / img.height
img.height = h
img.width = h * ratio
}
// Set the image document width to specified w.
func (img *image) SetWidth(w float64) {
// SetWidth set the Image's document width to specified w. This does not change the raw image data, i.e.
// no actual scaling of data is performed. That is handled by the PDF viewer.
func (img *Image) SetWidth(w float64) {
img.width = w
}
// Set the image document height to specified h.
func (img *image) SetHeight(h float64) {
// SetHeight sets the Image's document height to specified h.
func (img *Image) SetHeight(h float64) {
img.height = h
}
// Set image rotation angle in degrees.
func (img *image) SetAngle(angle float64) {
// SetAngle sets Image rotation angle in degrees.
func (img *Image) SetAngle(angle float64) {
img.angle = angle
}
// Draw the image onto the specified blk.
func drawImageOnBlock(blk *Block, img *image, ctx DrawContext) (DrawContext, error) {
func drawImageOnBlock(blk *Block, img *Image, ctx DrawContext) (DrawContext, error) {
origCtx := ctx
// Find a free name for the image.
@ -296,9 +303,9 @@ func drawImageOnBlock(blk *Block, img *image, ctx DrawContext) (DrawContext, err
blk.addContents(ops)
ctx.Y += img.Height()
if img.positioning.isRelative() {
ctx.Y += img.Height()
ctx.Height -= img.Height()
return ctx, nil
} else {
// Absolute positioning - return original context.

View File

@ -12,12 +12,10 @@ import (
"github.com/unidoc/unidoc/pdf/model"
)
//
// Defines a line between point 1 (X1,Y1) and point 2 (X2,Y2). The line ending styles can be none (regular line),
// Line defines a line between point 1 (X1,Y1) and point 2 (X2,Y2). The line ending styles can be none (regular line),
// or arrows at either end. The line also has a specified width, color and opacity.
// Implements the Drawable interface and can be drawn on PDF using the Creator.
//
type line struct {
type Line struct {
x1 float64
y1 float64
x2 float64
@ -26,9 +24,9 @@ type line struct {
lineWidth float64
}
// Generate a new line with default parameters between (x1,y1) to (x2,y2).
func NewLine(x1, y1, x2, y2 float64) *line {
l := &line{}
// NewLine creates a new Line with default parameters between (x1,y1) to (x2,y2).
func NewLine(x1, y1, x2, y2 float64) *Line {
l := &Line{}
l.x1 = x1
l.y1 = y1
@ -41,29 +39,29 @@ func NewLine(x1, y1, x2, y2 float64) *line {
return l
}
// Get the (x1, y1), (x2, y2) points defining the line.
func (l *line) GetCoords() (float64, float64, float64, float64) {
// GetCoords returns the (x1, y1), (x2, y2) points defining the Line.
func (l *Line) GetCoords() (float64, float64, float64, float64) {
return l.x1, l.y1, l.x2, l.y2
}
// Set line width.
func (l *line) SetLineWidth(lw float64) {
// SetLineWidth sets the line width.
func (l *Line) SetLineWidth(lw float64) {
l.lineWidth = lw
}
// Set line color.
// SetColor sets the line color.
// Use ColorRGBFromHex, ColorRGBFrom8bit or ColorRGBFromArithmetic to make the color object.
func (l *line) SetColor(col color) {
func (l *Line) SetColor(col Color) {
l.lineColor = model.NewPdfColorDeviceRGB(col.ToRGB())
}
// Calculate line length.
func (l *line) Length() float64 {
// Length calculates and returns the line length.
func (l *Line) Length() float64 {
return math.Sqrt(math.Pow(l.x2-l.x1, 2.0) + math.Pow(l.y2-l.y1, 2.0))
}
// Draws the line on a new block representing the page.
func (l *line) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
// GeneratePageBlocks draws the line on a new block representing the page. Implements the Drawable interface.
func (l *Line) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
block := NewBlock(ctx.PageWidth, ctx.PageHeight)
drawline := draw.Line{

View File

@ -17,9 +17,9 @@ import (
"github.com/unidoc/unidoc/pdf/model/textencoding"
)
// A paragraph represents text drawn with a specified font and can wrap across lines and pages.
// Paragraph represents text drawn with a specified font and can wrap across lines and pages.
// By default occupies the available width in the drawing context.
type paragraph struct {
type Paragraph struct {
// The input utf-8 text as a string (series of runes).
text string
@ -65,10 +65,10 @@ type paragraph struct {
textLines []string
}
// Create a new text block. Uses default parameters: Helvetica, WinAnsiEncoding and wrap enabled
// NewParagraph create a new text paragraph. Uses default parameters: Helvetica, WinAnsiEncoding and wrap enabled
// with a wrap width of 100 points.
func NewParagraph(text string) *paragraph {
p := &paragraph{}
func NewParagraph(text string) *Paragraph {
p := &Paragraph{}
p.text = text
p.textFont = fonts.NewFontHelvetica()
p.SetEncoder(textencoding.NewWinAnsiTextEncoder())
@ -89,92 +89,96 @@ func NewParagraph(text string) *paragraph {
return p
}
func (p *paragraph) SetFont(font fonts.Font) {
// SetFont sets the Paragraph's font.
func (p *Paragraph) SetFont(font fonts.Font) {
p.textFont = font
}
func (p *paragraph) SetFontSize(fontSize float64) {
// SetFontSize sets the font size in document units (points).
func (p *Paragraph) SetFontSize(fontSize float64) {
p.fontSize = fontSize
}
// Alignment of the text within the width provided.
func (p *paragraph) SetTextAlignment(align TextAlignment) {
// SetTextAlignment sets the horizontal alignment of the text within the space provided.
func (p *Paragraph) SetTextAlignment(align TextAlignment) {
p.alignment = align
}
// Set text encoding.
func (p *paragraph) SetEncoder(encoder textencoding.TextEncoder) {
// SetEncoder sets the text encoding.
func (p *Paragraph) SetEncoder(encoder textencoding.TextEncoder) {
p.encoder = encoder
// Sync with the text font too.
// XXX/FIXME: Keep in 1 place only.
p.textFont.SetEncoder(encoder)
}
func (p *paragraph) SetLineHeight(lineheight float64) {
// SetLineHeight sets the line height (1.0 default).
func (p *Paragraph) SetLineHeight(lineheight float64) {
p.lineHeight = lineheight
}
func (p *paragraph) SetText(text string) {
// SetText sets the text content of the Paragraph.
func (p *Paragraph) SetText(text string) {
p.text = text
}
// Set line wrapping enabled flag.
func (p *paragraph) SetEnableWrap(enableWrap bool) {
// SetEnableWrap sets the line wrapping enabled flag.
func (p *Paragraph) SetEnableWrap(enableWrap bool) {
p.enableWrap = enableWrap
}
// Set color of paragraph text.
// SetColor set the color of the Paragraph text.
//
// Example:
// 1. p := NewParagraph("Red paragraph")
// // Set to red color with a hex code:
// p.SetColor(creator.ColorRGBFromHex("#ff0000"))
//
// 2. Make paragraph green with 8-bit rgb values (0-255 each component)
// 2. Make Paragraph green with 8-bit rgb values (0-255 each component)
// p.SetColor(creator.ColorRGBFrom8bit(0, 255, 0)
//
// 3. Make paragraph blue with arithmetic (0-1) rgb components.
// 3. Make Paragraph blue with arithmetic (0-1) rgb components.
// p.SetColor(creator.ColorRGBFromArithmetic(0, 0, 1.0)
//
func (p *paragraph) SetColor(col color) {
func (p *Paragraph) SetColor(col Color) {
pdfColor := model.NewPdfColorDeviceRGB(col.ToRGB())
p.color = *pdfColor
}
// Drawable interface implementations.
// Set absolute positioning with specified coordinates.
func (p *paragraph) SetPos(x, y float64) {
// SetPos sets absolute positioning with specified coordinates.
func (p *Paragraph) SetPos(x, y float64) {
p.positioning = positionAbsolute
p.xPos = x
p.yPos = y
}
// Set rotation angle.
func (p *paragraph) SetAngle(angle float64) {
// SetAngle sets the rotation angle of the text.
func (p *Paragraph) SetAngle(angle float64) {
p.angle = angle
}
// Set paragraph Margins.
func (p *paragraph) SetMargins(left, right, top, bottom float64) {
// SetMargins sets the Paragraph's margins.
func (p *Paragraph) SetMargins(left, right, top, bottom float64) {
p.margins.left = left
p.margins.right = right
p.margins.top = top
p.margins.bottom = bottom
}
// Get paragraph Margins: left, right, top, bottom.
func (p *paragraph) GetMargins() (float64, float64, float64, float64) {
// GetMargins returns the Paragraph's margins: left, right, top, bottom.
func (p *Paragraph) GetMargins() (float64, float64, float64, float64) {
return p.margins.left, p.margins.right, p.margins.top, p.margins.bottom
}
// Set the paragraph width. Esentially the wrapping width, the width the text can extend to prior to wrapping.
func (p *paragraph) SetWidth(width float64) {
// SetWidth sets the the Paragraph width. This is essentially the wrapping width, i.e. the width the text can extend to
// prior to wrapping over to next line.
func (p *Paragraph) SetWidth(width float64) {
p.wrapWidth = width
p.wrapText()
}
func (p *paragraph) Width() float64 {
// Width returns the width of the Paragraph.
func (p *Paragraph) Width() float64 {
if p.enableWrap {
return p.wrapWidth
} else {
@ -182,9 +186,9 @@ func (p *paragraph) Width() float64 {
}
}
// The height is calculated based on the input text and how it is wrapped within the container.
// Height does not include Margins.
func (p *paragraph) Height() float64 {
// Height returns the height of the Paragraph. The height is calculated based on the input text and how it is wrapped
// within the container. Does not include Margins.
func (p *Paragraph) Height() float64 {
if p.textLines == nil || len(p.textLines) == 0 {
p.wrapText()
}
@ -193,23 +197,8 @@ func (p *paragraph) Height() float64 {
return h
}
func (p *paragraph) Scale(sx, sy float64) {
p.scaleX = sx
p.scaleY = sy
}
func (p *paragraph) ScaleToHeight(h float64) {
ratio := h / p.Height()
p.Scale(ratio, ratio)
}
func (p *paragraph) ScaleToWidth(w float64) {
ratio := w / p.Width()
p.Scale(ratio, ratio)
}
// Calculate the text width (if not wrapped).
func (p *paragraph) getTextWidth() float64 {
func (p *Paragraph) getTextWidth() float64 {
w := float64(0.0)
for _, rune := range p.text {
@ -232,7 +221,7 @@ func (p *paragraph) getTextWidth() float64 {
// Simple algorithm to wrap the text into lines (greedy algorithm - fill the lines).
// XXX/TODO: Consider the Knuth/Plass algorithm or an alternative.
func (p *paragraph) wrapText() error {
func (p *Paragraph) wrapText() error {
if !p.enableWrap {
p.textLines = []string{p.encoder.Encode(p.text)}
return nil
@ -308,15 +297,15 @@ func (p *paragraph) wrapText() error {
return nil
}
// Generate the Page blocks. Multiple blocks are generated if the contents wrap over
// multiple pages.
func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
// GeneratePageBlocks generates the page blocks. Multiple blocks are generated if the contents wrap over
// multiple pages. Implements the Drawable interface.
func (p *Paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
origContext := ctx
blocks := []*Block{}
blk := NewBlock(ctx.PageWidth, ctx.PageHeight)
if p.positioning.isRelative() {
// Account for paragraph Margins.
// Account for Paragraph Margins.
ctx.X += p.margins.left
ctx.Y += p.margins.top
ctx.Width -= p.margins.left + p.margins.right
@ -328,7 +317,7 @@ func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
if p.Height() > ctx.Height {
// Goes out of the bounds. Write on a new template instead and create a new context at upper
// left corner.
// XXX/TODO: Handle case when paragraph is larger than the Page...
// XXX/TODO: Handle case when Paragraph is larger than the Page...
// Should be fine if we just break on the paragraph, i.e. splitting it up over 2+ pages
blocks = append(blocks, blk)
@ -353,7 +342,7 @@ func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
ctx.Y = p.yPos
}
// Place the paragraph on the template at position (x,y) based on the ctx.
// Place the Paragraph on the template at position (x,y) based on the ctx.
ctx, err := drawParagraphOnBlock(blk, p, ctx)
if err != nil {
common.Log.Debug("ERROR: %v", err)
@ -363,6 +352,7 @@ func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
blocks = append(blocks, blk)
if p.positioning.isRelative() {
ctx.X -= p.margins.left // Move back.
ctx.Width = origContext.Width
return blocks, ctx, nil
} else {
// Absolute: not changing the context.
@ -371,7 +361,7 @@ func (p *paragraph) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
}
// Draw block on specified location on Page, adding to the content stream.
func drawParagraphOnBlock(blk *Block, p *paragraph, ctx DrawContext) (DrawContext, error) {
func drawParagraphOnBlock(blk *Block, p *Paragraph, ctx DrawContext) (DrawContext, error) {
// Find a free name for the font.
num := 1
fontName := core.PdfObjectName(fmt.Sprintf("Font%d", num))

View File

@ -10,12 +10,10 @@ import (
"github.com/unidoc/unidoc/pdf/model"
)
//
// Defines a rectangle with upper left corner at (x,y) and a specified width and height. The rectangle
// Rectangle defines a rectangle with upper left corner at (x,y) and a specified width and height. The rectangle
// can have a colored fill and/or border with a specified width.
// Implements the Drawable interface and can be drawn on PDF using the Creator.
//
type rectangle struct {
type Rectangle struct {
x float64 // Upper left corner
y float64
width float64
@ -25,9 +23,9 @@ type rectangle struct {
borderWidth float64
}
// Generate a new line with default parameters between (x1,y1) to (x2,y2).
func NewRectangle(x, y, width, height float64) *rectangle {
rect := &rectangle{}
// NewRectangle creates a new Rectangle with default parameters with left corner at (x,y) and width, height as specified.
func NewRectangle(x, y, width, height float64) *Rectangle {
rect := &Rectangle{}
rect.x = x
rect.y = y
@ -40,28 +38,28 @@ func NewRectangle(x, y, width, height float64) *rectangle {
return rect
}
// Get the coords of the upper left corner (x,y).
func (rect *rectangle) GetCoords() (float64, float64) {
// GetCoords returns coordinates of the Rectangle's upper left corner (x,y).
func (rect *Rectangle) GetCoords() (float64, float64) {
return rect.x, rect.y
}
// Set border width.
func (rect *rectangle) SetBorderWidth(bw float64) {
// SetBorderWidth sets the border width.
func (rect *Rectangle) SetBorderWidth(bw float64) {
rect.borderWidth = bw
}
// Set border color.
func (rect *rectangle) SetBorderColor(col color) {
// SetBorderColor sets border color.
func (rect *Rectangle) SetBorderColor(col Color) {
rect.borderColor = model.NewPdfColorDeviceRGB(col.ToRGB())
}
// Set fill color.
func (rect *rectangle) SetFillColor(col color) {
// SetFillColor sets the fill color.
func (rect *Rectangle) SetFillColor(col Color) {
rect.fillColor = model.NewPdfColorDeviceRGB(col.ToRGB())
}
// Draws the rectangle on a new block representing the page.
func (rect *rectangle) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
// GeneratePageBlocks draws the rectangle on a new block representing the page. Implements the Drawable interface.
func (rect *Rectangle) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
block := NewBlock(ctx.PageWidth, ctx.PageHeight)
drawrect := draw.Rectangle{

View File

@ -12,13 +12,13 @@ import (
"github.com/unidoc/unidoc/pdf/model/fonts"
)
// A subchapter simply represents a subchapter pertaining to a specific chapter. It can contain multiple
// drawables, just like a chapter.
type subchapter struct {
// Subchapter simply represents a sub chapter pertaining to a specific Chapter. It can contain multiple
// Drawables, just like a chapter.
type Subchapter struct {
chapterNum int
subchapterNum int
title string
heading *paragraph
heading *Paragraph
contents []Drawable
@ -41,8 +41,10 @@ type subchapter struct {
toc *TableOfContents
}
func (c *Creator) NewSubchapter(ch *Chapter, title string) *subchapter {
subchap := &subchapter{}
// NewSubchapter creates a new Subchapter under Chapter ch with specified title.
// All other parameters are set to their defaults.
func (c *Creator) NewSubchapter(ch *Chapter, title string) *Subchapter {
subchap := &Subchapter{}
ch.subchapters++
subchap.subchapterNum = ch.subchapters
@ -71,8 +73,8 @@ func (c *Creator) NewSubchapter(ch *Chapter, title string) *subchapter {
return subchap
}
// Set flag to indicate whether or not to show chapter numbers as part of title.
func (subchap *subchapter) SetShowNumbering(show bool) {
// SetShowNumbering sets a flag to indicate whether or not to show chapter numbers as part of title.
func (subchap *Subchapter) SetShowNumbering(show bool) {
if show {
heading := fmt.Sprintf("%d.%d. %s", subchap.chapterNum, subchap.subchapterNum, subchap.title)
subchap.heading.SetText(heading)
@ -83,13 +85,13 @@ func (subchap *subchapter) SetShowNumbering(show bool) {
subchap.showNumbering = show
}
// Set flag to indicate whether or not to include in the table of contents.
func (subchap *subchapter) SetIncludeInTOC(includeInTOC bool) {
// SetIncludeInTOC sets a flag to indicate whether or not to include in the table of contents.
func (subchap *Subchapter) SetIncludeInTOC(includeInTOC bool) {
subchap.includeInTOC = includeInTOC
}
// Get access to the heading paragraph to address style etc.
func (subchap *subchapter) GetHeading() *paragraph {
// GetHeading returns the Subchapter's heading Paragraph to address style (font type, size, etc).
func (subchap *Subchapter) GetHeading() *Paragraph {
return subchap.heading
}
@ -102,34 +104,36 @@ func (subchap *subchapter) SetPos(x, y float64) {
}
*/
// Set chapter Margins. Typically not needed as the Page Margins are used.
func (subchap *subchapter) SetMargins(left, right, top, bottom float64) {
// SetMargins sets the Subchapter's margins (left, right, top, bottom).
// These margins are typically not needed as the Creator's page margins are used preferably.
func (subchap *Subchapter) SetMargins(left, right, top, bottom float64) {
subchap.margins.left = left
subchap.margins.right = right
subchap.margins.top = top
subchap.margins.bottom = bottom
}
// Get the subchapter Margins: left, right, top, bototm.
func (subchap *subchapter) GetMargins() (float64, float64, float64, float64) {
// GetMargins returns the Subchapter's margins: left, right, top, bottom.
func (subchap *Subchapter) GetMargins() (float64, float64, float64, float64) {
return subchap.margins.left, subchap.margins.right, subchap.margins.top, subchap.margins.bottom
}
// Add a new drawable to the chapter.
func (subchap *subchapter) Add(d Drawable) {
// Add adds a new Drawable to the chapter.
// The currently supported Drawables are: *Paragraph, *Image, *Block, *Table.
func (subchap *Subchapter) Add(d Drawable) {
switch d.(type) {
case *Chapter, *subchapter:
case *Chapter, *Subchapter:
common.Log.Debug("Error: Cannot add chapter or subchapter to a subchapter")
case *paragraph, *image, *Block, *Table:
case *Paragraph, *Image, *Block, *Table:
subchap.contents = append(subchap.contents, d)
default:
common.Log.Debug("Unsupported: %T", d)
}
}
// Generate the Page blocks. Multiple blocks are generated if the contents wrap over
// multiple pages.
func (subchap *subchapter) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
// GeneratePageBlocks generates the page blocks. Multiple blocks are generated if the contents wrap over
// multiple pages. Implements the Drawable interface.
func (subchap *Subchapter) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
origCtx := ctx
if subchap.positioning.isRelative() {

View File

@ -33,7 +33,7 @@ type Table struct {
defaultRowHeight float64
// Content cells.
cells []*tableCell
cells []*TableCell
// Positioning: relative / absolute.
positioning positioning
@ -45,7 +45,7 @@ type Table struct {
margins margins
}
// Create a new table with a fixed rows and column size.
// NewTable create a new Table with a specified number of columns.
func NewTable(cols int) *Table {
t := &Table{}
t.rows = 0
@ -66,13 +66,14 @@ func NewTable(cols int) *Table {
// XXX/TODO: Base on contents instead?
t.defaultRowHeight = 10.0
t.cells = []*tableCell{}
t.cells = []*TableCell{}
return t
}
// Set the fractional column widths. Number of width inputs must match number of columns.
// SetColumnWidths sets the fractional column widths.
// Each width should be in the range 0-1 and is a fraction of the table width.
// The number of width inputs must match number of columns, otherwise an error is returned.
func (table *Table) SetColumnWidths(widths ...float64) error {
if len(widths) != table.cols {
common.Log.Debug("Mismatching number of widths and columns")
@ -84,7 +85,7 @@ func (table *Table) SetColumnWidths(widths ...float64) error {
return nil
}
// Total height of all rows.
// Height returns the total height of all rows.
func (table *Table) Height() float64 {
sum := float64(0.0)
for _, h := range table.rowHeights {
@ -94,7 +95,7 @@ func (table *Table) Height() float64 {
return sum
}
// Set the left, right, top, bottom Margins.
// SetMargins sets the Table's left, right, top, bottom margins.
func (table *Table) SetMargins(left, right, top, bottom float64) {
table.margins.left = left
table.margins.right = right
@ -102,12 +103,12 @@ func (table *Table) SetMargins(left, right, top, bottom float64) {
table.margins.bottom = bottom
}
// Get the left, right, top, bottom Margins.
// GetMargins returns the left, right, top, bottom Margins.
func (table *Table) GetMargins() (float64, float64, float64, float64) {
return table.margins.left, table.margins.right, table.margins.top, table.margins.bottom
}
// Set the height for a specified row.
// SetRowHeight sets the height for a specified row.
func (table *Table) SetRowHeight(row int, h float64) error {
if row < 1 || row > len(table.rowHeights) {
return errors.New("Range check error")
@ -117,30 +118,30 @@ func (table *Table) SetRowHeight(row int, h float64) error {
return nil
}
// Get row of the current cell position.
// CurRow returns the currently active cell's row number.
func (table *Table) CurRow() int {
curRow := (table.curCell-1)/table.cols + 1
return curRow
}
// Get column of the current cell position.
// CurCol returns the currently active cell's column number.
func (table *Table) CurCol() int {
curCol := (table.curCell-1)%(table.cols) + 1
return curCol
}
// Set absolute coordinates.
// SetPos sets the Table's positioning to absolute mode and specifies the upper-left corner coordinates as (x,y).
// Note that this is only sensible to use when the table does not wrap over multiple pages.
// XXX/TODO: Should be able to set width too (not just based on context/relative positioning mode).
// TODO: Should be able to set width too (not just based on context/relative positioning mode).
func (table *Table) SetPos(x, y float64) {
table.positioning = positionAbsolute
table.xPos = x
table.yPos = y
}
// Generate the Page blocks. Multiple blocks are generated if the contents wrap over multiple pages.
// GeneratePageBlocks generate the page blocks. Multiple blocks are generated if the contents wrap over multiple pages.
// Implements the Drawable interface.
func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext, error) {
blocks := []*Block{}
block := NewBlock(ctx.PageWidth, ctx.PageHeight)
@ -311,7 +312,7 @@ func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
return blocks, ctx, nil
}
// Define table cell's border style.
// CellBorderStyle defines the table cell's border style.
type CellBorderStyle int
// Currently supported table styles are: None (no border) and boxed (line along each side).
@ -323,9 +324,10 @@ const (
CellBorderStyleBox
)
// Define table cell's horizontal alignment.
// CellHorizontalAlignment defines the table cell's horizontal alignment.
type CellHorizontalAlignment int
// Table cells have three horizontal alignment modes: left, center and right.
const (
// Align cell content on the left (with specified indent); unused space on the right.
CellHorizontalAlignmentLeft CellHorizontalAlignment = iota
@ -337,9 +339,10 @@ const (
CellHorizontalAlignmentRight
)
// Define table cell's vertical alignment.
// CellVerticalAlignment defines the table cell's vertical alignment.
type CellVerticalAlignment int
// Table cells have three vertical alignment modes: top, middle and bottom.
const (
// Align cell content vertically to the top; unused space below.
CellVerticalAlignmentTop CellVerticalAlignment = iota
@ -351,8 +354,8 @@ const (
CellVerticalAlignmentBottom
)
// Table cell
type tableCell struct {
// TableCell defines a table cell which can contain a Drawable as content.
type TableCell struct {
// Background
backgroundColor *model.PdfColorDeviceRGB
@ -382,8 +385,8 @@ type tableCell struct {
table *Table
}
// Make a new cell and insert into the table at current position in the table.
func (table *Table) NewCell() *tableCell {
// NewCell makes a new cell and inserts into the table at current position in the table.
func (table *Table) NewCell() *TableCell {
table.curCell++
curRow := (table.curCell-1)/table.cols + 1
@ -393,7 +396,7 @@ func (table *Table) NewCell() *tableCell {
}
curCol := (table.curCell-1)%(table.cols) + 1
cell := &tableCell{}
cell := &TableCell{}
cell.row = curRow
cell.col = curCol
@ -418,7 +421,7 @@ func (table *Table) NewCell() *tableCell {
return cell
}
// Skip over a specified number of cells.
// SkipCells skips over a specified number of cells in the table.
func (table *Table) SkipCells(num int) {
if num < 0 {
common.Log.Debug("Table: cannot skip back to previous cells")
@ -427,7 +430,7 @@ func (table *Table) SkipCells(num int) {
table.curCell += num
}
// Skip over a specified number of rows.
// SkipRows skips over a specified number of rows in the table.
func (table *Table) SkipRows(num int) {
ncells := num*table.cols - 1
if ncells < 0 {
@ -437,7 +440,7 @@ func (table *Table) SkipRows(num int) {
table.curCell += ncells
}
// Skip over rows, cols.
// SkipOver skips over a specified number of rows and cols.
func (table *Table) SkipOver(rows, cols int) {
ncells := rows*table.cols + cols - 1
if ncells < 0 {
@ -447,47 +450,47 @@ func (table *Table) SkipOver(rows, cols int) {
table.curCell += ncells
}
// Set cell's left indent.
func (cell *tableCell) SetIndent(indent float64) {
// SetIndent sets the cell's left indent.
func (cell *TableCell) SetIndent(indent float64) {
cell.indent = indent
}
// Set cell's horizontal alignment of content.
// SetHorizontalAlignment sets the cell's horizontal alignment of content.
// Can be one of:
// - CellHorizontalAlignmentLeft
// - CellHorizontalAlignmentCenter
// - CellHorizontalAlignmentRight
func (cell *tableCell) SetHorizontalAlignment(halign CellHorizontalAlignment) {
func (cell *TableCell) SetHorizontalAlignment(halign CellHorizontalAlignment) {
cell.horizontalAlignment = halign
}
// Set cell's vertical alignment of content.
// SetVerticalAlignment set the cell's vertical alignment of content.
// Can be one of:
// - CellHorizontalAlignmentTop
// - CellHorizontalAlignmentMiddle
// - CellHorizontalAlignmentBottom
func (cell *tableCell) SetVerticalAlignment(valign CellVerticalAlignment) {
func (cell *TableCell) SetVerticalAlignment(valign CellVerticalAlignment) {
cell.verticalAlignment = valign
}
// Set cell's border style.
func (cell *tableCell) SetBorder(style CellBorderStyle, width float64) {
// SetBorder sets the cell's border style.
func (cell *TableCell) SetBorder(style CellBorderStyle, width float64) {
cell.borderStyle = style
cell.borderWidth = width
}
// Set border color.
func (cell *tableCell) SetBorderColor(color rgbColor) {
// SetBorderColor sets the cell's border color.
func (cell *TableCell) SetBorderColor(color rgbColor) {
cell.borderColor = model.NewPdfColorDeviceRGB(color.r, color.g, color.b)
}
// Set cell's background color.
func (cell *tableCell) SetBackgroundColor(col color) {
// SetBackgroundColor sets the cell's background color.
func (cell *TableCell) SetBackgroundColor(col Color) {
cell.backgroundColor = model.NewPdfColorDeviceRGB(col.ToRGB())
}
// Get cell width based on input draw context.
func (cell *tableCell) Width(ctx DrawContext) float64 {
// Width returns the cell's width based on the input draw context.
func (cell *TableCell) Width(ctx DrawContext) float64 {
fraction := float64(0.0)
for j := 0; j < cell.colspan; j++ {
fraction += cell.table.colWidths[cell.col+j-1]
@ -496,10 +499,12 @@ func (cell *tableCell) Width(ctx DrawContext) float64 {
return w
}
// Set cell content. The content is a vector drawable, i.e. a drawable with a known height and width.
func (cell *tableCell) SetContent(vd VectorDrawable) error {
// SetContent sets the cell's content. The content is a VectorDrawable, i.e. a Drawable with a known height and width.
// The currently supported VectorDrawable is: *Paragraph.
// TODO: Add support for *Image, *Block.
func (cell *TableCell) SetContent(vd VectorDrawable) error {
switch t := vd.(type) {
case *paragraph:
case *Paragraph:
// Default paragraph settings in table:
t.SetEnableWrap(false) // No wrapping.
h := cell.table.rowHeights[cell.row-1]

View File

@ -5,26 +5,26 @@
package creator
// The table of contents has overview over chapters and subchapters.
// TableOfContents provides an overview over chapters and subchapters when creating a document with Creator.
type TableOfContents struct {
entries []tableOfContentsEntry
entries []TableOfContentsEntry
}
// Make a new table of contents.
func newTableOfContents() *TableOfContents {
toc := TableOfContents{}
toc.entries = []tableOfContentsEntry{}
toc.entries = []TableOfContentsEntry{}
return &toc
}
// Get table of content entries.
func (toc *TableOfContents) Entries() []tableOfContentsEntry {
// Entries returns the table of content entries.
func (toc *TableOfContents) Entries() []TableOfContentsEntry {
return toc.entries
}
// Add a TOC entry.
func (toc *TableOfContents) add(title string, chapter, subchapter, pageNum int) {
entry := tableOfContentsEntry{}
entry := TableOfContentsEntry{}
entry.Title = title
entry.Chapter = chapter
entry.Subchapter = subchapter
@ -33,8 +33,9 @@ func (toc *TableOfContents) add(title string, chapter, subchapter, pageNum int)
toc.entries = append(toc.entries, entry)
}
// Each TOC entry has title, chapter number, sub chapter (0 if chapter) and the page number.
type tableOfContentsEntry struct {
// TableOfContentsEntry defines a single entry in the TableOfContents.
// Each entry has a title, chapter number, sub chapter (0 if chapter) and the page number.
type TableOfContentsEntry struct {
Title string
Chapter int
Subchapter int // 0 if chapter

View File

@ -1,80 +0,0 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package pdf
import (
"bytes"
"image"
"image/draw"
_ "image/gif"
"image/jpeg"
_ "image/png"
"io"
"github.com/unidoc/unidoc/common"
)
// Basic representation of an image. The images are stored in the JPEG
// format.
type Image struct {
Width int64
Height int64
Data *bytes.Buffer
}
type ImageHandler interface {
// Read any image type and load into a new Image object.
Read(r io.Reader) (*Image, error)
// Compress an image.
Compress(input *Image, quality int64) (*Image, error)
}
// Default implementation.
type DefaultImageHandler struct{}
func (this DefaultImageHandler) Read(reader io.Reader) (*Image, error) {
// Load the image with the native implementation.
img, _, err := image.Decode(reader)
if err != nil {
common.Log.Error("Error decoding file: %s", err)
return nil, err
}
// Write image stream.
var buf bytes.Buffer
opt := jpeg.Options{}
// Use full quality.
opt.Quality = 100
// Speed up jpeg encoding by converting to RGBA first.
// Will not be required once the golang image/jpeg package is optimized.
b := img.Bounds()
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
draw.Draw(m, m.Bounds(), img, b.Min, draw.Src)
err = jpeg.Encode(&buf, m, &opt)
if err != nil {
return nil, err
}
imag := Image{}
imag.Width = int64(b.Dx())
imag.Height = int64(b.Dy())
imag.Data = &buf
return &imag, nil
}
// To be implemented.
func (this DefaultImageHandler) Compress(input *Image, quality int64) (*Image, error) {
return input, nil
}
var ImageHandling ImageHandler = DefaultImageHandler{}
func SetImageHandler(imgHandling ImageHandler) {
ImageHandling = imgHandling
}

View File

@ -938,18 +938,18 @@ func (r *PdfReader) newPdfAnnotationMarkupFromDict(d *PdfObjectDictionary) (*Pdf
if _, isNull := obj.(*PdfObjectNull); !isNull {
return nil, fmt.Errorf("Popup should point to an indirect object")
}
}
} else {
popupAnnotObj, err := r.newPdfAnnotationFromIndirectObject(indObj)
if err != nil {
return nil, err
}
popupAnnot, isPopupAnnot := popupAnnotObj.context.(*PdfAnnotationPopup)
if !isPopupAnnot {
return nil, fmt.Errorf("Popup not referring to a popup annotation!")
}
popupAnnotObj, err := r.newPdfAnnotationFromIndirectObject(indObj)
if err != nil {
return nil, err
annot.Popup = popupAnnot
}
popupAnnot, isPopupAnnot := popupAnnotObj.context.(*PdfAnnotationPopup)
if !isPopupAnnot {
return nil, fmt.Errorf("Popup not referring to a popup annotation!")
}
annot.Popup = popupAnnot
}
if obj := d.Get("CA"); obj != nil {

View File

@ -138,6 +138,55 @@ func newPdfColorspaceFromPdfObject(obj PdfObject) (PdfColorspace, error) {
return nil, errors.New("Type error")
}
// determine PDF colorspace from a PdfObject. Returns the colorspace name and an error on failure.
// If the colorspace was not found, will return an empty string.
func determineColorspaceNameFromPdfObject(obj PdfObject) (PdfObjectName, error) {
var csName *PdfObjectName
var csArray *PdfObjectArray
if indObj, is := obj.(*PdfIndirectObject); is {
if array, is := indObj.PdfObject.(*PdfObjectArray); is {
csArray = array
} else if name, is := indObj.PdfObject.(*PdfObjectName); is {
csName = name
}
} else if array, is := obj.(*PdfObjectArray); is {
csArray = array
} else if name, is := obj.(*PdfObjectName); is {
csName = name
}
// If specified by a name directly: Device colorspace or Pattern.
if csName != nil {
switch *csName {
case "DeviceGray", "DeviceRGB", "DeviceCMYK":
return *csName, nil
case "Pattern":
return *csName, nil
}
}
if csArray != nil && len(*csArray) > 0 {
if name, is := (*csArray)[0].(*PdfObjectName); is {
switch *name {
case "DeviceGray", "DeviceRGB", "DeviceCMYK":
if len(*csArray) == 1 {
return *name, nil
}
case "CalGray", "CalRGB", "Lab":
return *name, nil
case "ICCBased", "Pattern", "Indexed":
return *name, nil
case "Separation", "DeviceN":
return *name, nil
}
}
}
// Not found
return "", nil
}
// Gray scale component.
// No specific parameters
@ -544,7 +593,7 @@ func (this *PdfColorspaceDeviceCMYK) ImageToRGB(img Image) (Image, error) {
decode = []float64{0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0}
}
if len(decode) != 8 {
common.Log.Debug("Invalid decode array (%d): % d", len(decode), decode)
common.Log.Debug("Invalid decode array (%d): % .3f", len(decode), decode)
return img, errors.New("Invalid decode array")
}
common.Log.Trace("Decode array: % f", decode)
@ -763,7 +812,7 @@ func (this *PdfColorspaceCalGray) ColorFromFloats(vals []float64) (PdfColor, err
}
func (this *PdfColorspaceCalGray) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
if len(objects) != 4 {
if len(objects) != 1 {
return nil, errors.New("Range check")
}
@ -1367,19 +1416,34 @@ func (this *PdfColorspaceLab) ColorFromFloats(vals []float64) (PdfColor, error)
// L
l := vals[0]
if l < 0.0 || l > 1.0 {
if l < 0.0 || l > 100.0 {
common.Log.Debug("L out of range (got %v should be 0-100)", l)
return nil, errors.New("Range check")
}
// A
a := vals[1]
if a < 0.0 || a > 1.0 {
aMin := float64(-100)
aMax := float64(100)
if len(this.Range) > 1 {
aMin = this.Range[0]
aMax = this.Range[1]
}
if a < aMin || a > aMax {
common.Log.Debug("A out of range (got %v; range %v to %v)", a, aMin, aMax)
return nil, errors.New("Range check")
}
// B.
b := vals[2]
if b < 0.0 || b > 1.0 {
bMin := float64(-100)
bMax := float64(100)
if len(this.Range) > 3 {
bMin = this.Range[2]
bMax = this.Range[3]
}
if b < bMin || b > bMax {
common.Log.Debug("b out of range (got %v; range %v to %v)", b, bMin, bMax)
return nil, errors.New("Range check")
}
@ -1416,6 +1480,8 @@ func (this *PdfColorspaceLab) ColorToRGB(color PdfColor) (PdfColor, error) {
}
// Get normalized L*, a*, b* values. [0-1]
// XXX/FIXME: According to the pdf standard these values are going to be in range 0-100, -100 - 100, -100 - 100
// by default.
LNorm := lab.L()
ANorm := lab.A()
BNorm := lab.B()
@ -1937,7 +2003,6 @@ func (this *PdfColorspaceSpecialPattern) ColorFromFloats(vals []float64) (PdfCol
// the name of the pattern.
func (this *PdfColorspaceSpecialPattern) ColorFromPdfObjects(objects []PdfObject) (PdfColor, error) {
if len(objects) < 1 {
common.Log.Error("ColorFromPdfObjects: len(objects)=%d", len(objects))
return nil, errors.New("Invalid number of parameters")
}
patternColor := &PdfColorPattern{}
@ -2053,6 +2118,14 @@ func newPdfColorspaceSpecialIndexedFromPdfObject(obj PdfObject) (*PdfColorspaceS
// Get base colormap.
obj = (*array)[1]
// Base cs cannot be another /Indexed or /Pattern space.
baseName, err := determineColorspaceNameFromPdfObject(obj)
if baseName == "Indexed" || baseName == "Pattern" {
common.Log.Debug("Error: Indexed colorspace cannot have Indexed/Pattern CS as base (%v)", baseName)
return nil, ErrRangeError
}
baseCs, err := newPdfColorspaceFromPdfObject(obj)
if err != nil {
return nil, err
@ -2154,7 +2227,15 @@ func (this *PdfColorspaceSpecialIndexed) ColorToRGB(color PdfColor) (PdfColor, e
// Convert an indexed image to RGB.
func (this *PdfColorspaceSpecialIndexed) ImageToRGB(img Image) (Image, error) {
baseImage := img
//baseImage := img
// Make a new representation of the image to be converted with the base colorspace.
baseImage := Image{}
baseImage.Height = img.Height
baseImage.Width = img.Width
baseImage.alphaData = img.alphaData
baseImage.BitsPerComponent = img.BitsPerComponent
baseImage.hasAlpha = img.hasAlpha
baseImage.ColorComponents = img.ColorComponents
samples := img.GetSamples()
N := this.Base.GetNumComponents()
@ -2318,11 +2399,15 @@ func (this *PdfColorspaceSpecialSeparation) ColorFromFloats(vals []float64) (Pdf
input := []float64{tint}
output, err := this.TintTransform.Evaluate(input)
if err != nil {
common.Log.Debug("Error, failed to evaluate: %v", err)
common.Log.Trace("Tint transform: %+v", this.TintTransform)
return nil, err
}
common.Log.Trace("Processing ColorFromFloats(%+v) on AlternateSpace: %#v", output, this.AlternateSpace)
color, err := this.AlternateSpace.ColorFromFloats(output)
if err != nil {
common.Log.Debug("Error, failed to evaluate in alternate space: %v", err)
return nil, err
}
@ -2416,6 +2501,7 @@ func (this *PdfColorspaceDeviceN) String() string {
return "DeviceN"
}
// GetNumComponents returns the number of input color components, i.e. that are input to the tint transform.
func (this *PdfColorspaceDeviceN) GetNumComponents() int {
return len(*this.ColorantNames)
}

View File

@ -1,3 +1,5 @@
// +build unidev
// Parse character metrics from an AFM file to convert into a static go code declaration.
package main
@ -56,7 +58,7 @@ func runCharmetricsOnFile(path string) error {
}
keys := []string{}
for key, _ := range metrics {
for key := range metrics {
keys = append(keys, key)
}
@ -77,7 +79,7 @@ func runCharcodeToGlyphRetrievalOnFile(afmpath string) error {
}
keys := []int{}
for key, _ := range charcodeToGlyphMap {
for key := range charcodeToGlyphMap {
keys = append(keys, int(key))
}
sort.Ints(keys)
@ -98,7 +100,7 @@ func runGlyphToCharcodeRetrievalOnFile(afmpath string) error {
}
keys := []int{}
for key, _ := range charcodeToGlyphMap {
for key := range charcodeToGlyphMap {
keys = append(keys, int(key))
}
sort.Ints(keys)

View File

@ -54,319 +54,319 @@ func (font fontHelvetica) ToPdfObject() core.PdfObject {
// Helvetica font metics loaded from afms/Helvetica.afm. See afms/MustRead.html for license information.
var helveticaCharMetrics map[string]CharMetrics = map[string]CharMetrics{
"A": CharMetrics{GlyphName: "A", Wx: 667.000000, Wy: 0.000000},
"AE": CharMetrics{GlyphName: "AE", Wx: 1000.000000, Wy: 0.000000},
"Aacute": CharMetrics{GlyphName: "Aacute", Wx: 667.000000, Wy: 0.000000},
"Abreve": CharMetrics{GlyphName: "Abreve", Wx: 667.000000, Wy: 0.000000},
"Acircumflex": CharMetrics{GlyphName: "Acircumflex", Wx: 667.000000, Wy: 0.000000},
"Adieresis": CharMetrics{GlyphName: "Adieresis", Wx: 667.000000, Wy: 0.000000},
"Agrave": CharMetrics{GlyphName: "Agrave", Wx: 667.000000, Wy: 0.000000},
"Amacron": CharMetrics{GlyphName: "Amacron", Wx: 667.000000, Wy: 0.000000},
"Aogonek": CharMetrics{GlyphName: "Aogonek", Wx: 667.000000, Wy: 0.000000},
"Aring": CharMetrics{GlyphName: "Aring", Wx: 667.000000, Wy: 0.000000},
"Atilde": CharMetrics{GlyphName: "Atilde", Wx: 667.000000, Wy: 0.000000},
"B": CharMetrics{GlyphName: "B", Wx: 667.000000, Wy: 0.000000},
"C": CharMetrics{GlyphName: "C", Wx: 722.000000, Wy: 0.000000},
"Cacute": CharMetrics{GlyphName: "Cacute", Wx: 722.000000, Wy: 0.000000},
"Ccaron": CharMetrics{GlyphName: "Ccaron", Wx: 722.000000, Wy: 0.000000},
"Ccedilla": CharMetrics{GlyphName: "Ccedilla", Wx: 722.000000, Wy: 0.000000},
"D": CharMetrics{GlyphName: "D", Wx: 722.000000, Wy: 0.000000},
"Dcaron": CharMetrics{GlyphName: "Dcaron", Wx: 722.000000, Wy: 0.000000},
"Dcroat": CharMetrics{GlyphName: "Dcroat", Wx: 722.000000, Wy: 0.000000},
"Delta": CharMetrics{GlyphName: "Delta", Wx: 612.000000, Wy: 0.000000},
"E": CharMetrics{GlyphName: "E", Wx: 667.000000, Wy: 0.000000},
"Eacute": CharMetrics{GlyphName: "Eacute", Wx: 667.000000, Wy: 0.000000},
"Ecaron": CharMetrics{GlyphName: "Ecaron", Wx: 667.000000, Wy: 0.000000},
"Ecircumflex": CharMetrics{GlyphName: "Ecircumflex", Wx: 667.000000, Wy: 0.000000},
"Edieresis": CharMetrics{GlyphName: "Edieresis", Wx: 667.000000, Wy: 0.000000},
"Edotaccent": CharMetrics{GlyphName: "Edotaccent", Wx: 667.000000, Wy: 0.000000},
"Egrave": CharMetrics{GlyphName: "Egrave", Wx: 667.000000, Wy: 0.000000},
"Emacron": CharMetrics{GlyphName: "Emacron", Wx: 667.000000, Wy: 0.000000},
"Eogonek": CharMetrics{GlyphName: "Eogonek", Wx: 667.000000, Wy: 0.000000},
"Eth": CharMetrics{GlyphName: "Eth", Wx: 722.000000, Wy: 0.000000},
"Euro": CharMetrics{GlyphName: "Euro", Wx: 556.000000, Wy: 0.000000},
"F": CharMetrics{GlyphName: "F", Wx: 611.000000, Wy: 0.000000},
"G": CharMetrics{GlyphName: "G", Wx: 778.000000, Wy: 0.000000},
"Gbreve": CharMetrics{GlyphName: "Gbreve", Wx: 778.000000, Wy: 0.000000},
"Gcommaaccent": CharMetrics{GlyphName: "Gcommaaccent", Wx: 778.000000, Wy: 0.000000},
"H": CharMetrics{GlyphName: "H", Wx: 722.000000, Wy: 0.000000},
"I": CharMetrics{GlyphName: "I", Wx: 278.000000, Wy: 0.000000},
"Iacute": CharMetrics{GlyphName: "Iacute", Wx: 278.000000, Wy: 0.000000},
"Icircumflex": CharMetrics{GlyphName: "Icircumflex", Wx: 278.000000, Wy: 0.000000},
"Idieresis": CharMetrics{GlyphName: "Idieresis", Wx: 278.000000, Wy: 0.000000},
"Idotaccent": CharMetrics{GlyphName: "Idotaccent", Wx: 278.000000, Wy: 0.000000},
"Igrave": CharMetrics{GlyphName: "Igrave", Wx: 278.000000, Wy: 0.000000},
"Imacron": CharMetrics{GlyphName: "Imacron", Wx: 278.000000, Wy: 0.000000},
"Iogonek": CharMetrics{GlyphName: "Iogonek", Wx: 278.000000, Wy: 0.000000},
"J": CharMetrics{GlyphName: "J", Wx: 500.000000, Wy: 0.000000},
"K": CharMetrics{GlyphName: "K", Wx: 667.000000, Wy: 0.000000},
"Kcommaaccent": CharMetrics{GlyphName: "Kcommaaccent", Wx: 667.000000, Wy: 0.000000},
"L": CharMetrics{GlyphName: "L", Wx: 556.000000, Wy: 0.000000},
"Lacute": CharMetrics{GlyphName: "Lacute", Wx: 556.000000, Wy: 0.000000},
"Lcaron": CharMetrics{GlyphName: "Lcaron", Wx: 556.000000, Wy: 0.000000},
"Lcommaaccent": CharMetrics{GlyphName: "Lcommaaccent", Wx: 556.000000, Wy: 0.000000},
"Lslash": CharMetrics{GlyphName: "Lslash", Wx: 556.000000, Wy: 0.000000},
"M": CharMetrics{GlyphName: "M", Wx: 833.000000, Wy: 0.000000},
"N": CharMetrics{GlyphName: "N", Wx: 722.000000, Wy: 0.000000},
"Nacute": CharMetrics{GlyphName: "Nacute", Wx: 722.000000, Wy: 0.000000},
"Ncaron": CharMetrics{GlyphName: "Ncaron", Wx: 722.000000, Wy: 0.000000},
"Ncommaaccent": CharMetrics{GlyphName: "Ncommaaccent", Wx: 722.000000, Wy: 0.000000},
"Ntilde": CharMetrics{GlyphName: "Ntilde", Wx: 722.000000, Wy: 0.000000},
"O": CharMetrics{GlyphName: "O", Wx: 778.000000, Wy: 0.000000},
"OE": CharMetrics{GlyphName: "OE", Wx: 1000.000000, Wy: 0.000000},
"Oacute": CharMetrics{GlyphName: "Oacute", Wx: 778.000000, Wy: 0.000000},
"Ocircumflex": CharMetrics{GlyphName: "Ocircumflex", Wx: 778.000000, Wy: 0.000000},
"Odieresis": CharMetrics{GlyphName: "Odieresis", Wx: 778.000000, Wy: 0.000000},
"Ograve": CharMetrics{GlyphName: "Ograve", Wx: 778.000000, Wy: 0.000000},
"Ohungarumlaut": CharMetrics{GlyphName: "Ohungarumlaut", Wx: 778.000000, Wy: 0.000000},
"Omacron": CharMetrics{GlyphName: "Omacron", Wx: 778.000000, Wy: 0.000000},
"Oslash": CharMetrics{GlyphName: "Oslash", Wx: 778.000000, Wy: 0.000000},
"Otilde": CharMetrics{GlyphName: "Otilde", Wx: 778.000000, Wy: 0.000000},
"P": CharMetrics{GlyphName: "P", Wx: 667.000000, Wy: 0.000000},
"Q": CharMetrics{GlyphName: "Q", Wx: 778.000000, Wy: 0.000000},
"R": CharMetrics{GlyphName: "R", Wx: 722.000000, Wy: 0.000000},
"Racute": CharMetrics{GlyphName: "Racute", Wx: 722.000000, Wy: 0.000000},
"Rcaron": CharMetrics{GlyphName: "Rcaron", Wx: 722.000000, Wy: 0.000000},
"Rcommaaccent": CharMetrics{GlyphName: "Rcommaaccent", Wx: 722.000000, Wy: 0.000000},
"S": CharMetrics{GlyphName: "S", Wx: 667.000000, Wy: 0.000000},
"Sacute": CharMetrics{GlyphName: "Sacute", Wx: 667.000000, Wy: 0.000000},
"Scaron": CharMetrics{GlyphName: "Scaron", Wx: 667.000000, Wy: 0.000000},
"Scedilla": CharMetrics{GlyphName: "Scedilla", Wx: 667.000000, Wy: 0.000000},
"Scommaaccent": CharMetrics{GlyphName: "Scommaaccent", Wx: 667.000000, Wy: 0.000000},
"T": CharMetrics{GlyphName: "T", Wx: 611.000000, Wy: 0.000000},
"Tcaron": CharMetrics{GlyphName: "Tcaron", Wx: 611.000000, Wy: 0.000000},
"Tcommaaccent": CharMetrics{GlyphName: "Tcommaaccent", Wx: 611.000000, Wy: 0.000000},
"Thorn": CharMetrics{GlyphName: "Thorn", Wx: 667.000000, Wy: 0.000000},
"U": CharMetrics{GlyphName: "U", Wx: 722.000000, Wy: 0.000000},
"Uacute": CharMetrics{GlyphName: "Uacute", Wx: 722.000000, Wy: 0.000000},
"Ucircumflex": CharMetrics{GlyphName: "Ucircumflex", Wx: 722.000000, Wy: 0.000000},
"Udieresis": CharMetrics{GlyphName: "Udieresis", Wx: 722.000000, Wy: 0.000000},
"Ugrave": CharMetrics{GlyphName: "Ugrave", Wx: 722.000000, Wy: 0.000000},
"Uhungarumlaut": CharMetrics{GlyphName: "Uhungarumlaut", Wx: 722.000000, Wy: 0.000000},
"Umacron": CharMetrics{GlyphName: "Umacron", Wx: 722.000000, Wy: 0.000000},
"Uogonek": CharMetrics{GlyphName: "Uogonek", Wx: 722.000000, Wy: 0.000000},
"Uring": CharMetrics{GlyphName: "Uring", Wx: 722.000000, Wy: 0.000000},
"V": CharMetrics{GlyphName: "V", Wx: 667.000000, Wy: 0.000000},
"W": CharMetrics{GlyphName: "W", Wx: 944.000000, Wy: 0.000000},
"X": CharMetrics{GlyphName: "X", Wx: 667.000000, Wy: 0.000000},
"Y": CharMetrics{GlyphName: "Y", Wx: 667.000000, Wy: 0.000000},
"Yacute": CharMetrics{GlyphName: "Yacute", Wx: 667.000000, Wy: 0.000000},
"Ydieresis": CharMetrics{GlyphName: "Ydieresis", Wx: 667.000000, Wy: 0.000000},
"Z": CharMetrics{GlyphName: "Z", Wx: 611.000000, Wy: 0.000000},
"Zacute": CharMetrics{GlyphName: "Zacute", Wx: 611.000000, Wy: 0.000000},
"Zcaron": CharMetrics{GlyphName: "Zcaron", Wx: 611.000000, Wy: 0.000000},
"Zdotaccent": CharMetrics{GlyphName: "Zdotaccent", Wx: 611.000000, Wy: 0.000000},
"a": CharMetrics{GlyphName: "a", Wx: 556.000000, Wy: 0.000000},
"aacute": CharMetrics{GlyphName: "aacute", Wx: 556.000000, Wy: 0.000000},
"abreve": CharMetrics{GlyphName: "abreve", Wx: 556.000000, Wy: 0.000000},
"acircumflex": CharMetrics{GlyphName: "acircumflex", Wx: 556.000000, Wy: 0.000000},
"acute": CharMetrics{GlyphName: "acute", Wx: 333.000000, Wy: 0.000000},
"adieresis": CharMetrics{GlyphName: "adieresis", Wx: 556.000000, Wy: 0.000000},
"ae": CharMetrics{GlyphName: "ae", Wx: 889.000000, Wy: 0.000000},
"agrave": CharMetrics{GlyphName: "agrave", Wx: 556.000000, Wy: 0.000000},
"amacron": CharMetrics{GlyphName: "amacron", Wx: 556.000000, Wy: 0.000000},
"ampersand": CharMetrics{GlyphName: "ampersand", Wx: 667.000000, Wy: 0.000000},
"aogonek": CharMetrics{GlyphName: "aogonek", Wx: 556.000000, Wy: 0.000000},
"aring": CharMetrics{GlyphName: "aring", Wx: 556.000000, Wy: 0.000000},
"asciicircum": CharMetrics{GlyphName: "asciicircum", Wx: 469.000000, Wy: 0.000000},
"asciitilde": CharMetrics{GlyphName: "asciitilde", Wx: 584.000000, Wy: 0.000000},
"asterisk": CharMetrics{GlyphName: "asterisk", Wx: 389.000000, Wy: 0.000000},
"at": CharMetrics{GlyphName: "at", Wx: 1015.000000, Wy: 0.000000},
"atilde": CharMetrics{GlyphName: "atilde", Wx: 556.000000, Wy: 0.000000},
"b": CharMetrics{GlyphName: "b", Wx: 556.000000, Wy: 0.000000},
"backslash": CharMetrics{GlyphName: "backslash", Wx: 278.000000, Wy: 0.000000},
"bar": CharMetrics{GlyphName: "bar", Wx: 260.000000, Wy: 0.000000},
"braceleft": CharMetrics{GlyphName: "braceleft", Wx: 334.000000, Wy: 0.000000},
"braceright": CharMetrics{GlyphName: "braceright", Wx: 334.000000, Wy: 0.000000},
"bracketleft": CharMetrics{GlyphName: "bracketleft", Wx: 278.000000, Wy: 0.000000},
"bracketright": CharMetrics{GlyphName: "bracketright", Wx: 278.000000, Wy: 0.000000},
"breve": CharMetrics{GlyphName: "breve", Wx: 333.000000, Wy: 0.000000},
"brokenbar": CharMetrics{GlyphName: "brokenbar", Wx: 260.000000, Wy: 0.000000},
"bullet": CharMetrics{GlyphName: "bullet", Wx: 350.000000, Wy: 0.000000},
"c": CharMetrics{GlyphName: "c", Wx: 500.000000, Wy: 0.000000},
"cacute": CharMetrics{GlyphName: "cacute", Wx: 500.000000, Wy: 0.000000},
"caron": CharMetrics{GlyphName: "caron", Wx: 333.000000, Wy: 0.000000},
"ccaron": CharMetrics{GlyphName: "ccaron", Wx: 500.000000, Wy: 0.000000},
"ccedilla": CharMetrics{GlyphName: "ccedilla", Wx: 500.000000, Wy: 0.000000},
"cedilla": CharMetrics{GlyphName: "cedilla", Wx: 333.000000, Wy: 0.000000},
"cent": CharMetrics{GlyphName: "cent", Wx: 556.000000, Wy: 0.000000},
"circumflex": CharMetrics{GlyphName: "circumflex", Wx: 333.000000, Wy: 0.000000},
"colon": CharMetrics{GlyphName: "colon", Wx: 278.000000, Wy: 0.000000},
"comma": CharMetrics{GlyphName: "comma", Wx: 278.000000, Wy: 0.000000},
"commaaccent": CharMetrics{GlyphName: "commaaccent", Wx: 250.000000, Wy: 0.000000},
"copyright": CharMetrics{GlyphName: "copyright", Wx: 737.000000, Wy: 0.000000},
"currency": CharMetrics{GlyphName: "currency", Wx: 556.000000, Wy: 0.000000},
"d": CharMetrics{GlyphName: "d", Wx: 556.000000, Wy: 0.000000},
"dagger": CharMetrics{GlyphName: "dagger", Wx: 556.000000, Wy: 0.000000},
"daggerdbl": CharMetrics{GlyphName: "daggerdbl", Wx: 556.000000, Wy: 0.000000},
"dcaron": CharMetrics{GlyphName: "dcaron", Wx: 643.000000, Wy: 0.000000},
"dcroat": CharMetrics{GlyphName: "dcroat", Wx: 556.000000, Wy: 0.000000},
"degree": CharMetrics{GlyphName: "degree", Wx: 400.000000, Wy: 0.000000},
"dieresis": CharMetrics{GlyphName: "dieresis", Wx: 333.000000, Wy: 0.000000},
"divide": CharMetrics{GlyphName: "divide", Wx: 584.000000, Wy: 0.000000},
"dollar": CharMetrics{GlyphName: "dollar", Wx: 556.000000, Wy: 0.000000},
"dotaccent": CharMetrics{GlyphName: "dotaccent", Wx: 333.000000, Wy: 0.000000},
"dotlessi": CharMetrics{GlyphName: "dotlessi", Wx: 278.000000, Wy: 0.000000},
"e": CharMetrics{GlyphName: "e", Wx: 556.000000, Wy: 0.000000},
"eacute": CharMetrics{GlyphName: "eacute", Wx: 556.000000, Wy: 0.000000},
"ecaron": CharMetrics{GlyphName: "ecaron", Wx: 556.000000, Wy: 0.000000},
"ecircumflex": CharMetrics{GlyphName: "ecircumflex", Wx: 556.000000, Wy: 0.000000},
"edieresis": CharMetrics{GlyphName: "edieresis", Wx: 556.000000, Wy: 0.000000},
"edotaccent": CharMetrics{GlyphName: "edotaccent", Wx: 556.000000, Wy: 0.000000},
"egrave": CharMetrics{GlyphName: "egrave", Wx: 556.000000, Wy: 0.000000},
"eight": CharMetrics{GlyphName: "eight", Wx: 556.000000, Wy: 0.000000},
"ellipsis": CharMetrics{GlyphName: "ellipsis", Wx: 1000.000000, Wy: 0.000000},
"emacron": CharMetrics{GlyphName: "emacron", Wx: 556.000000, Wy: 0.000000},
"emdash": CharMetrics{GlyphName: "emdash", Wx: 1000.000000, Wy: 0.000000},
"endash": CharMetrics{GlyphName: "endash", Wx: 556.000000, Wy: 0.000000},
"eogonek": CharMetrics{GlyphName: "eogonek", Wx: 556.000000, Wy: 0.000000},
"equal": CharMetrics{GlyphName: "equal", Wx: 584.000000, Wy: 0.000000},
"eth": CharMetrics{GlyphName: "eth", Wx: 556.000000, Wy: 0.000000},
"exclam": CharMetrics{GlyphName: "exclam", Wx: 278.000000, Wy: 0.000000},
"exclamdown": CharMetrics{GlyphName: "exclamdown", Wx: 333.000000, Wy: 0.000000},
"f": CharMetrics{GlyphName: "f", Wx: 278.000000, Wy: 0.000000},
"fi": CharMetrics{GlyphName: "fi", Wx: 500.000000, Wy: 0.000000},
"five": CharMetrics{GlyphName: "five", Wx: 556.000000, Wy: 0.000000},
"fl": CharMetrics{GlyphName: "fl", Wx: 500.000000, Wy: 0.000000},
"florin": CharMetrics{GlyphName: "florin", Wx: 556.000000, Wy: 0.000000},
"four": CharMetrics{GlyphName: "four", Wx: 556.000000, Wy: 0.000000},
"fraction": CharMetrics{GlyphName: "fraction", Wx: 167.000000, Wy: 0.000000},
"g": CharMetrics{GlyphName: "g", Wx: 556.000000, Wy: 0.000000},
"gbreve": CharMetrics{GlyphName: "gbreve", Wx: 556.000000, Wy: 0.000000},
"gcommaaccent": CharMetrics{GlyphName: "gcommaaccent", Wx: 556.000000, Wy: 0.000000},
"germandbls": CharMetrics{GlyphName: "germandbls", Wx: 611.000000, Wy: 0.000000},
"grave": CharMetrics{GlyphName: "grave", Wx: 333.000000, Wy: 0.000000},
"greater": CharMetrics{GlyphName: "greater", Wx: 584.000000, Wy: 0.000000},
"greaterequal": CharMetrics{GlyphName: "greaterequal", Wx: 549.000000, Wy: 0.000000},
"guillemotleft": CharMetrics{GlyphName: "guillemotleft", Wx: 556.000000, Wy: 0.000000},
"guillemotright": CharMetrics{GlyphName: "guillemotright", Wx: 556.000000, Wy: 0.000000},
"guilsinglleft": CharMetrics{GlyphName: "guilsinglleft", Wx: 333.000000, Wy: 0.000000},
"guilsinglright": CharMetrics{GlyphName: "guilsinglright", Wx: 333.000000, Wy: 0.000000},
"h": CharMetrics{GlyphName: "h", Wx: 556.000000, Wy: 0.000000},
"hungarumlaut": CharMetrics{GlyphName: "hungarumlaut", Wx: 333.000000, Wy: 0.000000},
"hyphen": CharMetrics{GlyphName: "hyphen", Wx: 333.000000, Wy: 0.000000},
"i": CharMetrics{GlyphName: "i", Wx: 222.000000, Wy: 0.000000},
"iacute": CharMetrics{GlyphName: "iacute", Wx: 278.000000, Wy: 0.000000},
"icircumflex": CharMetrics{GlyphName: "icircumflex", Wx: 278.000000, Wy: 0.000000},
"idieresis": CharMetrics{GlyphName: "idieresis", Wx: 278.000000, Wy: 0.000000},
"igrave": CharMetrics{GlyphName: "igrave", Wx: 278.000000, Wy: 0.000000},
"imacron": CharMetrics{GlyphName: "imacron", Wx: 278.000000, Wy: 0.000000},
"iogonek": CharMetrics{GlyphName: "iogonek", Wx: 222.000000, Wy: 0.000000},
"j": CharMetrics{GlyphName: "j", Wx: 222.000000, Wy: 0.000000},
"k": CharMetrics{GlyphName: "k", Wx: 500.000000, Wy: 0.000000},
"kcommaaccent": CharMetrics{GlyphName: "kcommaaccent", Wx: 500.000000, Wy: 0.000000},
"l": CharMetrics{GlyphName: "l", Wx: 222.000000, Wy: 0.000000},
"lacute": CharMetrics{GlyphName: "lacute", Wx: 222.000000, Wy: 0.000000},
"lcaron": CharMetrics{GlyphName: "lcaron", Wx: 299.000000, Wy: 0.000000},
"lcommaaccent": CharMetrics{GlyphName: "lcommaaccent", Wx: 222.000000, Wy: 0.000000},
"less": CharMetrics{GlyphName: "less", Wx: 584.000000, Wy: 0.000000},
"lessequal": CharMetrics{GlyphName: "lessequal", Wx: 549.000000, Wy: 0.000000},
"logicalnot": CharMetrics{GlyphName: "logicalnot", Wx: 584.000000, Wy: 0.000000},
"lozenge": CharMetrics{GlyphName: "lozenge", Wx: 471.000000, Wy: 0.000000},
"lslash": CharMetrics{GlyphName: "lslash", Wx: 222.000000, Wy: 0.000000},
"m": CharMetrics{GlyphName: "m", Wx: 833.000000, Wy: 0.000000},
"macron": CharMetrics{GlyphName: "macron", Wx: 333.000000, Wy: 0.000000},
"minus": CharMetrics{GlyphName: "minus", Wx: 584.000000, Wy: 0.000000},
"mu": CharMetrics{GlyphName: "mu", Wx: 556.000000, Wy: 0.000000},
"multiply": CharMetrics{GlyphName: "multiply", Wx: 584.000000, Wy: 0.000000},
"n": CharMetrics{GlyphName: "n", Wx: 556.000000, Wy: 0.000000},
"nacute": CharMetrics{GlyphName: "nacute", Wx: 556.000000, Wy: 0.000000},
"ncaron": CharMetrics{GlyphName: "ncaron", Wx: 556.000000, Wy: 0.000000},
"ncommaaccent": CharMetrics{GlyphName: "ncommaaccent", Wx: 556.000000, Wy: 0.000000},
"nine": CharMetrics{GlyphName: "nine", Wx: 556.000000, Wy: 0.000000},
"notequal": CharMetrics{GlyphName: "notequal", Wx: 549.000000, Wy: 0.000000},
"ntilde": CharMetrics{GlyphName: "ntilde", Wx: 556.000000, Wy: 0.000000},
"numbersign": CharMetrics{GlyphName: "numbersign", Wx: 556.000000, Wy: 0.000000},
"o": CharMetrics{GlyphName: "o", Wx: 556.000000, Wy: 0.000000},
"oacute": CharMetrics{GlyphName: "oacute", Wx: 556.000000, Wy: 0.000000},
"ocircumflex": CharMetrics{GlyphName: "ocircumflex", Wx: 556.000000, Wy: 0.000000},
"odieresis": CharMetrics{GlyphName: "odieresis", Wx: 556.000000, Wy: 0.000000},
"oe": CharMetrics{GlyphName: "oe", Wx: 944.000000, Wy: 0.000000},
"ogonek": CharMetrics{GlyphName: "ogonek", Wx: 333.000000, Wy: 0.000000},
"ograve": CharMetrics{GlyphName: "ograve", Wx: 556.000000, Wy: 0.000000},
"ohungarumlaut": CharMetrics{GlyphName: "ohungarumlaut", Wx: 556.000000, Wy: 0.000000},
"omacron": CharMetrics{GlyphName: "omacron", Wx: 556.000000, Wy: 0.000000},
"one": CharMetrics{GlyphName: "one", Wx: 556.000000, Wy: 0.000000},
"onehalf": CharMetrics{GlyphName: "onehalf", Wx: 834.000000, Wy: 0.000000},
"onequarter": CharMetrics{GlyphName: "onequarter", Wx: 834.000000, Wy: 0.000000},
"onesuperior": CharMetrics{GlyphName: "onesuperior", Wx: 333.000000, Wy: 0.000000},
"ordfeminine": CharMetrics{GlyphName: "ordfeminine", Wx: 370.000000, Wy: 0.000000},
"ordmasculine": CharMetrics{GlyphName: "ordmasculine", Wx: 365.000000, Wy: 0.000000},
"oslash": CharMetrics{GlyphName: "oslash", Wx: 611.000000, Wy: 0.000000},
"otilde": CharMetrics{GlyphName: "otilde", Wx: 556.000000, Wy: 0.000000},
"p": CharMetrics{GlyphName: "p", Wx: 556.000000, Wy: 0.000000},
"paragraph": CharMetrics{GlyphName: "paragraph", Wx: 537.000000, Wy: 0.000000},
"parenleft": CharMetrics{GlyphName: "parenleft", Wx: 333.000000, Wy: 0.000000},
"parenright": CharMetrics{GlyphName: "parenright", Wx: 333.000000, Wy: 0.000000},
"partialdiff": CharMetrics{GlyphName: "partialdiff", Wx: 476.000000, Wy: 0.000000},
"percent": CharMetrics{GlyphName: "percent", Wx: 889.000000, Wy: 0.000000},
"period": CharMetrics{GlyphName: "period", Wx: 278.000000, Wy: 0.000000},
"periodcentered": CharMetrics{GlyphName: "periodcentered", Wx: 278.000000, Wy: 0.000000},
"perthousand": CharMetrics{GlyphName: "perthousand", Wx: 1000.000000, Wy: 0.000000},
"plus": CharMetrics{GlyphName: "plus", Wx: 584.000000, Wy: 0.000000},
"plusminus": CharMetrics{GlyphName: "plusminus", Wx: 584.000000, Wy: 0.000000},
"q": CharMetrics{GlyphName: "q", Wx: 556.000000, Wy: 0.000000},
"question": CharMetrics{GlyphName: "question", Wx: 556.000000, Wy: 0.000000},
"questiondown": CharMetrics{GlyphName: "questiondown", Wx: 611.000000, Wy: 0.000000},
"quotedbl": CharMetrics{GlyphName: "quotedbl", Wx: 355.000000, Wy: 0.000000},
"quotedblbase": CharMetrics{GlyphName: "quotedblbase", Wx: 333.000000, Wy: 0.000000},
"quotedblleft": CharMetrics{GlyphName: "quotedblleft", Wx: 333.000000, Wy: 0.000000},
"quotedblright": CharMetrics{GlyphName: "quotedblright", Wx: 333.000000, Wy: 0.000000},
"quoteleft": CharMetrics{GlyphName: "quoteleft", Wx: 222.000000, Wy: 0.000000},
"quoteright": CharMetrics{GlyphName: "quoteright", Wx: 222.000000, Wy: 0.000000},
"quotesinglbase": CharMetrics{GlyphName: "quotesinglbase", Wx: 222.000000, Wy: 0.000000},
"quotesingle": CharMetrics{GlyphName: "quotesingle", Wx: 191.000000, Wy: 0.000000},
"r": CharMetrics{GlyphName: "r", Wx: 333.000000, Wy: 0.000000},
"racute": CharMetrics{GlyphName: "racute", Wx: 333.000000, Wy: 0.000000},
"radical": CharMetrics{GlyphName: "radical", Wx: 453.000000, Wy: 0.000000},
"rcaron": CharMetrics{GlyphName: "rcaron", Wx: 333.000000, Wy: 0.000000},
"rcommaaccent": CharMetrics{GlyphName: "rcommaaccent", Wx: 333.000000, Wy: 0.000000},
"registered": CharMetrics{GlyphName: "registered", Wx: 737.000000, Wy: 0.000000},
"ring": CharMetrics{GlyphName: "ring", Wx: 333.000000, Wy: 0.000000},
"s": CharMetrics{GlyphName: "s", Wx: 500.000000, Wy: 0.000000},
"sacute": CharMetrics{GlyphName: "sacute", Wx: 500.000000, Wy: 0.000000},
"scaron": CharMetrics{GlyphName: "scaron", Wx: 500.000000, Wy: 0.000000},
"scedilla": CharMetrics{GlyphName: "scedilla", Wx: 500.000000, Wy: 0.000000},
"scommaaccent": CharMetrics{GlyphName: "scommaaccent", Wx: 500.000000, Wy: 0.000000},
"section": CharMetrics{GlyphName: "section", Wx: 556.000000, Wy: 0.000000},
"semicolon": CharMetrics{GlyphName: "semicolon", Wx: 278.000000, Wy: 0.000000},
"seven": CharMetrics{GlyphName: "seven", Wx: 556.000000, Wy: 0.000000},
"six": CharMetrics{GlyphName: "six", Wx: 556.000000, Wy: 0.000000},
"slash": CharMetrics{GlyphName: "slash", Wx: 278.000000, Wy: 0.000000},
"space": CharMetrics{GlyphName: "space", Wx: 278.000000, Wy: 0.000000},
"sterling": CharMetrics{GlyphName: "sterling", Wx: 556.000000, Wy: 0.000000},
"summation": CharMetrics{GlyphName: "summation", Wx: 600.000000, Wy: 0.000000},
"t": CharMetrics{GlyphName: "t", Wx: 278.000000, Wy: 0.000000},
"tcaron": CharMetrics{GlyphName: "tcaron", Wx: 317.000000, Wy: 0.000000},
"tcommaaccent": CharMetrics{GlyphName: "tcommaaccent", Wx: 278.000000, Wy: 0.000000},
"thorn": CharMetrics{GlyphName: "thorn", Wx: 556.000000, Wy: 0.000000},
"three": CharMetrics{GlyphName: "three", Wx: 556.000000, Wy: 0.000000},
"threequarters": CharMetrics{GlyphName: "threequarters", Wx: 834.000000, Wy: 0.000000},
"threesuperior": CharMetrics{GlyphName: "threesuperior", Wx: 333.000000, Wy: 0.000000},
"tilde": CharMetrics{GlyphName: "tilde", Wx: 333.000000, Wy: 0.000000},
"trademark": CharMetrics{GlyphName: "trademark", Wx: 1000.000000, Wy: 0.000000},
"two": CharMetrics{GlyphName: "two", Wx: 556.000000, Wy: 0.000000},
"twosuperior": CharMetrics{GlyphName: "twosuperior", Wx: 333.000000, Wy: 0.000000},
"u": CharMetrics{GlyphName: "u", Wx: 556.000000, Wy: 0.000000},
"uacute": CharMetrics{GlyphName: "uacute", Wx: 556.000000, Wy: 0.000000},
"ucircumflex": CharMetrics{GlyphName: "ucircumflex", Wx: 556.000000, Wy: 0.000000},
"udieresis": CharMetrics{GlyphName: "udieresis", Wx: 556.000000, Wy: 0.000000},
"ugrave": CharMetrics{GlyphName: "ugrave", Wx: 556.000000, Wy: 0.000000},
"uhungarumlaut": CharMetrics{GlyphName: "uhungarumlaut", Wx: 556.000000, Wy: 0.000000},
"umacron": CharMetrics{GlyphName: "umacron", Wx: 556.000000, Wy: 0.000000},
"underscore": CharMetrics{GlyphName: "underscore", Wx: 556.000000, Wy: 0.000000},
"uogonek": CharMetrics{GlyphName: "uogonek", Wx: 556.000000, Wy: 0.000000},
"uring": CharMetrics{GlyphName: "uring", Wx: 556.000000, Wy: 0.000000},
"v": CharMetrics{GlyphName: "v", Wx: 500.000000, Wy: 0.000000},
"w": CharMetrics{GlyphName: "w", Wx: 722.000000, Wy: 0.000000},
"x": CharMetrics{GlyphName: "x", Wx: 500.000000, Wy: 0.000000},
"y": CharMetrics{GlyphName: "y", Wx: 500.000000, Wy: 0.000000},
"yacute": CharMetrics{GlyphName: "yacute", Wx: 500.000000, Wy: 0.000000},
"ydieresis": CharMetrics{GlyphName: "ydieresis", Wx: 500.000000, Wy: 0.000000},
"yen": CharMetrics{GlyphName: "yen", Wx: 556.000000, Wy: 0.000000},
"z": CharMetrics{GlyphName: "z", Wx: 500.000000, Wy: 0.000000},
"zacute": CharMetrics{GlyphName: "zacute", Wx: 500.000000, Wy: 0.000000},
"zcaron": CharMetrics{GlyphName: "zcaron", Wx: 500.000000, Wy: 0.000000},
"zdotaccent": CharMetrics{GlyphName: "zdotaccent", Wx: 500.000000, Wy: 0.000000},
"zero": CharMetrics{GlyphName: "zero", Wx: 556.000000, Wy: 0.000000},
"A": {GlyphName: "A", Wx: 667.000000, Wy: 0.000000},
"AE": {GlyphName: "AE", Wx: 1000.000000, Wy: 0.000000},
"Aacute": {GlyphName: "Aacute", Wx: 667.000000, Wy: 0.000000},
"Abreve": {GlyphName: "Abreve", Wx: 667.000000, Wy: 0.000000},
"Acircumflex": {GlyphName: "Acircumflex", Wx: 667.000000, Wy: 0.000000},
"Adieresis": {GlyphName: "Adieresis", Wx: 667.000000, Wy: 0.000000},
"Agrave": {GlyphName: "Agrave", Wx: 667.000000, Wy: 0.000000},
"Amacron": {GlyphName: "Amacron", Wx: 667.000000, Wy: 0.000000},
"Aogonek": {GlyphName: "Aogonek", Wx: 667.000000, Wy: 0.000000},
"Aring": {GlyphName: "Aring", Wx: 667.000000, Wy: 0.000000},
"Atilde": {GlyphName: "Atilde", Wx: 667.000000, Wy: 0.000000},
"B": {GlyphName: "B", Wx: 667.000000, Wy: 0.000000},
"C": {GlyphName: "C", Wx: 722.000000, Wy: 0.000000},
"Cacute": {GlyphName: "Cacute", Wx: 722.000000, Wy: 0.000000},
"Ccaron": {GlyphName: "Ccaron", Wx: 722.000000, Wy: 0.000000},
"Ccedilla": {GlyphName: "Ccedilla", Wx: 722.000000, Wy: 0.000000},
"D": {GlyphName: "D", Wx: 722.000000, Wy: 0.000000},
"Dcaron": {GlyphName: "Dcaron", Wx: 722.000000, Wy: 0.000000},
"Dcroat": {GlyphName: "Dcroat", Wx: 722.000000, Wy: 0.000000},
"Delta": {GlyphName: "Delta", Wx: 612.000000, Wy: 0.000000},
"E": {GlyphName: "E", Wx: 667.000000, Wy: 0.000000},
"Eacute": {GlyphName: "Eacute", Wx: 667.000000, Wy: 0.000000},
"Ecaron": {GlyphName: "Ecaron", Wx: 667.000000, Wy: 0.000000},
"Ecircumflex": {GlyphName: "Ecircumflex", Wx: 667.000000, Wy: 0.000000},
"Edieresis": {GlyphName: "Edieresis", Wx: 667.000000, Wy: 0.000000},
"Edotaccent": {GlyphName: "Edotaccent", Wx: 667.000000, Wy: 0.000000},
"Egrave": {GlyphName: "Egrave", Wx: 667.000000, Wy: 0.000000},
"Emacron": {GlyphName: "Emacron", Wx: 667.000000, Wy: 0.000000},
"Eogonek": {GlyphName: "Eogonek", Wx: 667.000000, Wy: 0.000000},
"Eth": {GlyphName: "Eth", Wx: 722.000000, Wy: 0.000000},
"Euro": {GlyphName: "Euro", Wx: 556.000000, Wy: 0.000000},
"F": {GlyphName: "F", Wx: 611.000000, Wy: 0.000000},
"G": {GlyphName: "G", Wx: 778.000000, Wy: 0.000000},
"Gbreve": {GlyphName: "Gbreve", Wx: 778.000000, Wy: 0.000000},
"Gcommaaccent": {GlyphName: "Gcommaaccent", Wx: 778.000000, Wy: 0.000000},
"H": {GlyphName: "H", Wx: 722.000000, Wy: 0.000000},
"I": {GlyphName: "I", Wx: 278.000000, Wy: 0.000000},
"Iacute": {GlyphName: "Iacute", Wx: 278.000000, Wy: 0.000000},
"Icircumflex": {GlyphName: "Icircumflex", Wx: 278.000000, Wy: 0.000000},
"Idieresis": {GlyphName: "Idieresis", Wx: 278.000000, Wy: 0.000000},
"Idotaccent": {GlyphName: "Idotaccent", Wx: 278.000000, Wy: 0.000000},
"Igrave": {GlyphName: "Igrave", Wx: 278.000000, Wy: 0.000000},
"Imacron": {GlyphName: "Imacron", Wx: 278.000000, Wy: 0.000000},
"Iogonek": {GlyphName: "Iogonek", Wx: 278.000000, Wy: 0.000000},
"J": {GlyphName: "J", Wx: 500.000000, Wy: 0.000000},
"K": {GlyphName: "K", Wx: 667.000000, Wy: 0.000000},
"Kcommaaccent": {GlyphName: "Kcommaaccent", Wx: 667.000000, Wy: 0.000000},
"L": {GlyphName: "L", Wx: 556.000000, Wy: 0.000000},
"Lacute": {GlyphName: "Lacute", Wx: 556.000000, Wy: 0.000000},
"Lcaron": {GlyphName: "Lcaron", Wx: 556.000000, Wy: 0.000000},
"Lcommaaccent": {GlyphName: "Lcommaaccent", Wx: 556.000000, Wy: 0.000000},
"Lslash": {GlyphName: "Lslash", Wx: 556.000000, Wy: 0.000000},
"M": {GlyphName: "M", Wx: 833.000000, Wy: 0.000000},
"N": {GlyphName: "N", Wx: 722.000000, Wy: 0.000000},
"Nacute": {GlyphName: "Nacute", Wx: 722.000000, Wy: 0.000000},
"Ncaron": {GlyphName: "Ncaron", Wx: 722.000000, Wy: 0.000000},
"Ncommaaccent": {GlyphName: "Ncommaaccent", Wx: 722.000000, Wy: 0.000000},
"Ntilde": {GlyphName: "Ntilde", Wx: 722.000000, Wy: 0.000000},
"O": {GlyphName: "O", Wx: 778.000000, Wy: 0.000000},
"OE": {GlyphName: "OE", Wx: 1000.000000, Wy: 0.000000},
"Oacute": {GlyphName: "Oacute", Wx: 778.000000, Wy: 0.000000},
"Ocircumflex": {GlyphName: "Ocircumflex", Wx: 778.000000, Wy: 0.000000},
"Odieresis": {GlyphName: "Odieresis", Wx: 778.000000, Wy: 0.000000},
"Ograve": {GlyphName: "Ograve", Wx: 778.000000, Wy: 0.000000},
"Ohungarumlaut": {GlyphName: "Ohungarumlaut", Wx: 778.000000, Wy: 0.000000},
"Omacron": {GlyphName: "Omacron", Wx: 778.000000, Wy: 0.000000},
"Oslash": {GlyphName: "Oslash", Wx: 778.000000, Wy: 0.000000},
"Otilde": {GlyphName: "Otilde", Wx: 778.000000, Wy: 0.000000},
"P": {GlyphName: "P", Wx: 667.000000, Wy: 0.000000},
"Q": {GlyphName: "Q", Wx: 778.000000, Wy: 0.000000},
"R": {GlyphName: "R", Wx: 722.000000, Wy: 0.000000},
"Racute": {GlyphName: "Racute", Wx: 722.000000, Wy: 0.000000},
"Rcaron": {GlyphName: "Rcaron", Wx: 722.000000, Wy: 0.000000},
"Rcommaaccent": {GlyphName: "Rcommaaccent", Wx: 722.000000, Wy: 0.000000},
"S": {GlyphName: "S", Wx: 667.000000, Wy: 0.000000},
"Sacute": {GlyphName: "Sacute", Wx: 667.000000, Wy: 0.000000},
"Scaron": {GlyphName: "Scaron", Wx: 667.000000, Wy: 0.000000},
"Scedilla": {GlyphName: "Scedilla", Wx: 667.000000, Wy: 0.000000},
"Scommaaccent": {GlyphName: "Scommaaccent", Wx: 667.000000, Wy: 0.000000},
"T": {GlyphName: "T", Wx: 611.000000, Wy: 0.000000},
"Tcaron": {GlyphName: "Tcaron", Wx: 611.000000, Wy: 0.000000},
"Tcommaaccent": {GlyphName: "Tcommaaccent", Wx: 611.000000, Wy: 0.000000},
"Thorn": {GlyphName: "Thorn", Wx: 667.000000, Wy: 0.000000},
"U": {GlyphName: "U", Wx: 722.000000, Wy: 0.000000},
"Uacute": {GlyphName: "Uacute", Wx: 722.000000, Wy: 0.000000},
"Ucircumflex": {GlyphName: "Ucircumflex", Wx: 722.000000, Wy: 0.000000},
"Udieresis": {GlyphName: "Udieresis", Wx: 722.000000, Wy: 0.000000},
"Ugrave": {GlyphName: "Ugrave", Wx: 722.000000, Wy: 0.000000},
"Uhungarumlaut": {GlyphName: "Uhungarumlaut", Wx: 722.000000, Wy: 0.000000},
"Umacron": {GlyphName: "Umacron", Wx: 722.000000, Wy: 0.000000},
"Uogonek": {GlyphName: "Uogonek", Wx: 722.000000, Wy: 0.000000},
"Uring": {GlyphName: "Uring", Wx: 722.000000, Wy: 0.000000},
"V": {GlyphName: "V", Wx: 667.000000, Wy: 0.000000},
"W": {GlyphName: "W", Wx: 944.000000, Wy: 0.000000},
"X": {GlyphName: "X", Wx: 667.000000, Wy: 0.000000},
"Y": {GlyphName: "Y", Wx: 667.000000, Wy: 0.000000},
"Yacute": {GlyphName: "Yacute", Wx: 667.000000, Wy: 0.000000},
"Ydieresis": {GlyphName: "Ydieresis", Wx: 667.000000, Wy: 0.000000},
"Z": {GlyphName: "Z", Wx: 611.000000, Wy: 0.000000},
"Zacute": {GlyphName: "Zacute", Wx: 611.000000, Wy: 0.000000},
"Zcaron": {GlyphName: "Zcaron", Wx: 611.000000, Wy: 0.000000},
"Zdotaccent": {GlyphName: "Zdotaccent", Wx: 611.000000, Wy: 0.000000},
"a": {GlyphName: "a", Wx: 556.000000, Wy: 0.000000},
"aacute": {GlyphName: "aacute", Wx: 556.000000, Wy: 0.000000},
"abreve": {GlyphName: "abreve", Wx: 556.000000, Wy: 0.000000},
"acircumflex": {GlyphName: "acircumflex", Wx: 556.000000, Wy: 0.000000},
"acute": {GlyphName: "acute", Wx: 333.000000, Wy: 0.000000},
"adieresis": {GlyphName: "adieresis", Wx: 556.000000, Wy: 0.000000},
"ae": {GlyphName: "ae", Wx: 889.000000, Wy: 0.000000},
"agrave": {GlyphName: "agrave", Wx: 556.000000, Wy: 0.000000},
"amacron": {GlyphName: "amacron", Wx: 556.000000, Wy: 0.000000},
"ampersand": {GlyphName: "ampersand", Wx: 667.000000, Wy: 0.000000},
"aogonek": {GlyphName: "aogonek", Wx: 556.000000, Wy: 0.000000},
"aring": {GlyphName: "aring", Wx: 556.000000, Wy: 0.000000},
"asciicircum": {GlyphName: "asciicircum", Wx: 469.000000, Wy: 0.000000},
"asciitilde": {GlyphName: "asciitilde", Wx: 584.000000, Wy: 0.000000},
"asterisk": {GlyphName: "asterisk", Wx: 389.000000, Wy: 0.000000},
"at": {GlyphName: "at", Wx: 1015.000000, Wy: 0.000000},
"atilde": {GlyphName: "atilde", Wx: 556.000000, Wy: 0.000000},
"b": {GlyphName: "b", Wx: 556.000000, Wy: 0.000000},
"backslash": {GlyphName: "backslash", Wx: 278.000000, Wy: 0.000000},
"bar": {GlyphName: "bar", Wx: 260.000000, Wy: 0.000000},
"braceleft": {GlyphName: "braceleft", Wx: 334.000000, Wy: 0.000000},
"braceright": {GlyphName: "braceright", Wx: 334.000000, Wy: 0.000000},
"bracketleft": {GlyphName: "bracketleft", Wx: 278.000000, Wy: 0.000000},
"bracketright": {GlyphName: "bracketright", Wx: 278.000000, Wy: 0.000000},
"breve": {GlyphName: "breve", Wx: 333.000000, Wy: 0.000000},
"brokenbar": {GlyphName: "brokenbar", Wx: 260.000000, Wy: 0.000000},
"bullet": {GlyphName: "bullet", Wx: 350.000000, Wy: 0.000000},
"c": {GlyphName: "c", Wx: 500.000000, Wy: 0.000000},
"cacute": {GlyphName: "cacute", Wx: 500.000000, Wy: 0.000000},
"caron": {GlyphName: "caron", Wx: 333.000000, Wy: 0.000000},
"ccaron": {GlyphName: "ccaron", Wx: 500.000000, Wy: 0.000000},
"ccedilla": {GlyphName: "ccedilla", Wx: 500.000000, Wy: 0.000000},
"cedilla": {GlyphName: "cedilla", Wx: 333.000000, Wy: 0.000000},
"cent": {GlyphName: "cent", Wx: 556.000000, Wy: 0.000000},
"circumflex": {GlyphName: "circumflex", Wx: 333.000000, Wy: 0.000000},
"colon": {GlyphName: "colon", Wx: 278.000000, Wy: 0.000000},
"comma": {GlyphName: "comma", Wx: 278.000000, Wy: 0.000000},
"commaaccent": {GlyphName: "commaaccent", Wx: 250.000000, Wy: 0.000000},
"copyright": {GlyphName: "copyright", Wx: 737.000000, Wy: 0.000000},
"currency": {GlyphName: "currency", Wx: 556.000000, Wy: 0.000000},
"d": {GlyphName: "d", Wx: 556.000000, Wy: 0.000000},
"dagger": {GlyphName: "dagger", Wx: 556.000000, Wy: 0.000000},
"daggerdbl": {GlyphName: "daggerdbl", Wx: 556.000000, Wy: 0.000000},
"dcaron": {GlyphName: "dcaron", Wx: 643.000000, Wy: 0.000000},
"dcroat": {GlyphName: "dcroat", Wx: 556.000000, Wy: 0.000000},
"degree": {GlyphName: "degree", Wx: 400.000000, Wy: 0.000000},
"dieresis": {GlyphName: "dieresis", Wx: 333.000000, Wy: 0.000000},
"divide": {GlyphName: "divide", Wx: 584.000000, Wy: 0.000000},
"dollar": {GlyphName: "dollar", Wx: 556.000000, Wy: 0.000000},
"dotaccent": {GlyphName: "dotaccent", Wx: 333.000000, Wy: 0.000000},
"dotlessi": {GlyphName: "dotlessi", Wx: 278.000000, Wy: 0.000000},
"e": {GlyphName: "e", Wx: 556.000000, Wy: 0.000000},
"eacute": {GlyphName: "eacute", Wx: 556.000000, Wy: 0.000000},
"ecaron": {GlyphName: "ecaron", Wx: 556.000000, Wy: 0.000000},
"ecircumflex": {GlyphName: "ecircumflex", Wx: 556.000000, Wy: 0.000000},
"edieresis": {GlyphName: "edieresis", Wx: 556.000000, Wy: 0.000000},
"edotaccent": {GlyphName: "edotaccent", Wx: 556.000000, Wy: 0.000000},
"egrave": {GlyphName: "egrave", Wx: 556.000000, Wy: 0.000000},
"eight": {GlyphName: "eight", Wx: 556.000000, Wy: 0.000000},
"ellipsis": {GlyphName: "ellipsis", Wx: 1000.000000, Wy: 0.000000},
"emacron": {GlyphName: "emacron", Wx: 556.000000, Wy: 0.000000},
"emdash": {GlyphName: "emdash", Wx: 1000.000000, Wy: 0.000000},
"endash": {GlyphName: "endash", Wx: 556.000000, Wy: 0.000000},
"eogonek": {GlyphName: "eogonek", Wx: 556.000000, Wy: 0.000000},
"equal": {GlyphName: "equal", Wx: 584.000000, Wy: 0.000000},
"eth": {GlyphName: "eth", Wx: 556.000000, Wy: 0.000000},
"exclam": {GlyphName: "exclam", Wx: 278.000000, Wy: 0.000000},
"exclamdown": {GlyphName: "exclamdown", Wx: 333.000000, Wy: 0.000000},
"f": {GlyphName: "f", Wx: 278.000000, Wy: 0.000000},
"fi": {GlyphName: "fi", Wx: 500.000000, Wy: 0.000000},
"five": {GlyphName: "five", Wx: 556.000000, Wy: 0.000000},
"fl": {GlyphName: "fl", Wx: 500.000000, Wy: 0.000000},
"florin": {GlyphName: "florin", Wx: 556.000000, Wy: 0.000000},
"four": {GlyphName: "four", Wx: 556.000000, Wy: 0.000000},
"fraction": {GlyphName: "fraction", Wx: 167.000000, Wy: 0.000000},
"g": {GlyphName: "g", Wx: 556.000000, Wy: 0.000000},
"gbreve": {GlyphName: "gbreve", Wx: 556.000000, Wy: 0.000000},
"gcommaaccent": {GlyphName: "gcommaaccent", Wx: 556.000000, Wy: 0.000000},
"germandbls": {GlyphName: "germandbls", Wx: 611.000000, Wy: 0.000000},
"grave": {GlyphName: "grave", Wx: 333.000000, Wy: 0.000000},
"greater": {GlyphName: "greater", Wx: 584.000000, Wy: 0.000000},
"greaterequal": {GlyphName: "greaterequal", Wx: 549.000000, Wy: 0.000000},
"guillemotleft": {GlyphName: "guillemotleft", Wx: 556.000000, Wy: 0.000000},
"guillemotright": {GlyphName: "guillemotright", Wx: 556.000000, Wy: 0.000000},
"guilsinglleft": {GlyphName: "guilsinglleft", Wx: 333.000000, Wy: 0.000000},
"guilsinglright": {GlyphName: "guilsinglright", Wx: 333.000000, Wy: 0.000000},
"h": {GlyphName: "h", Wx: 556.000000, Wy: 0.000000},
"hungarumlaut": {GlyphName: "hungarumlaut", Wx: 333.000000, Wy: 0.000000},
"hyphen": {GlyphName: "hyphen", Wx: 333.000000, Wy: 0.000000},
"i": {GlyphName: "i", Wx: 222.000000, Wy: 0.000000},
"iacute": {GlyphName: "iacute", Wx: 278.000000, Wy: 0.000000},
"icircumflex": {GlyphName: "icircumflex", Wx: 278.000000, Wy: 0.000000},
"idieresis": {GlyphName: "idieresis", Wx: 278.000000, Wy: 0.000000},
"igrave": {GlyphName: "igrave", Wx: 278.000000, Wy: 0.000000},
"imacron": {GlyphName: "imacron", Wx: 278.000000, Wy: 0.000000},
"iogonek": {GlyphName: "iogonek", Wx: 222.000000, Wy: 0.000000},
"j": {GlyphName: "j", Wx: 222.000000, Wy: 0.000000},
"k": {GlyphName: "k", Wx: 500.000000, Wy: 0.000000},
"kcommaaccent": {GlyphName: "kcommaaccent", Wx: 500.000000, Wy: 0.000000},
"l": {GlyphName: "l", Wx: 222.000000, Wy: 0.000000},
"lacute": {GlyphName: "lacute", Wx: 222.000000, Wy: 0.000000},
"lcaron": {GlyphName: "lcaron", Wx: 299.000000, Wy: 0.000000},
"lcommaaccent": {GlyphName: "lcommaaccent", Wx: 222.000000, Wy: 0.000000},
"less": {GlyphName: "less", Wx: 584.000000, Wy: 0.000000},
"lessequal": {GlyphName: "lessequal", Wx: 549.000000, Wy: 0.000000},
"logicalnot": {GlyphName: "logicalnot", Wx: 584.000000, Wy: 0.000000},
"lozenge": {GlyphName: "lozenge", Wx: 471.000000, Wy: 0.000000},
"lslash": {GlyphName: "lslash", Wx: 222.000000, Wy: 0.000000},
"m": {GlyphName: "m", Wx: 833.000000, Wy: 0.000000},
"macron": {GlyphName: "macron", Wx: 333.000000, Wy: 0.000000},
"minus": {GlyphName: "minus", Wx: 584.000000, Wy: 0.000000},
"mu": {GlyphName: "mu", Wx: 556.000000, Wy: 0.000000},
"multiply": {GlyphName: "multiply", Wx: 584.000000, Wy: 0.000000},
"n": {GlyphName: "n", Wx: 556.000000, Wy: 0.000000},
"nacute": {GlyphName: "nacute", Wx: 556.000000, Wy: 0.000000},
"ncaron": {GlyphName: "ncaron", Wx: 556.000000, Wy: 0.000000},
"ncommaaccent": {GlyphName: "ncommaaccent", Wx: 556.000000, Wy: 0.000000},
"nine": {GlyphName: "nine", Wx: 556.000000, Wy: 0.000000},
"notequal": {GlyphName: "notequal", Wx: 549.000000, Wy: 0.000000},
"ntilde": {GlyphName: "ntilde", Wx: 556.000000, Wy: 0.000000},
"numbersign": {GlyphName: "numbersign", Wx: 556.000000, Wy: 0.000000},
"o": {GlyphName: "o", Wx: 556.000000, Wy: 0.000000},
"oacute": {GlyphName: "oacute", Wx: 556.000000, Wy: 0.000000},
"ocircumflex": {GlyphName: "ocircumflex", Wx: 556.000000, Wy: 0.000000},
"odieresis": {GlyphName: "odieresis", Wx: 556.000000, Wy: 0.000000},
"oe": {GlyphName: "oe", Wx: 944.000000, Wy: 0.000000},
"ogonek": {GlyphName: "ogonek", Wx: 333.000000, Wy: 0.000000},
"ograve": {GlyphName: "ograve", Wx: 556.000000, Wy: 0.000000},
"ohungarumlaut": {GlyphName: "ohungarumlaut", Wx: 556.000000, Wy: 0.000000},
"omacron": {GlyphName: "omacron", Wx: 556.000000, Wy: 0.000000},
"one": {GlyphName: "one", Wx: 556.000000, Wy: 0.000000},
"onehalf": {GlyphName: "onehalf", Wx: 834.000000, Wy: 0.000000},
"onequarter": {GlyphName: "onequarter", Wx: 834.000000, Wy: 0.000000},
"onesuperior": {GlyphName: "onesuperior", Wx: 333.000000, Wy: 0.000000},
"ordfeminine": {GlyphName: "ordfeminine", Wx: 370.000000, Wy: 0.000000},
"ordmasculine": {GlyphName: "ordmasculine", Wx: 365.000000, Wy: 0.000000},
"oslash": {GlyphName: "oslash", Wx: 611.000000, Wy: 0.000000},
"otilde": {GlyphName: "otilde", Wx: 556.000000, Wy: 0.000000},
"p": {GlyphName: "p", Wx: 556.000000, Wy: 0.000000},
"paragraph": {GlyphName: "paragraph", Wx: 537.000000, Wy: 0.000000},
"parenleft": {GlyphName: "parenleft", Wx: 333.000000, Wy: 0.000000},
"parenright": {GlyphName: "parenright", Wx: 333.000000, Wy: 0.000000},
"partialdiff": {GlyphName: "partialdiff", Wx: 476.000000, Wy: 0.000000},
"percent": {GlyphName: "percent", Wx: 889.000000, Wy: 0.000000},
"period": {GlyphName: "period", Wx: 278.000000, Wy: 0.000000},
"periodcentered": {GlyphName: "periodcentered", Wx: 278.000000, Wy: 0.000000},
"perthousand": {GlyphName: "perthousand", Wx: 1000.000000, Wy: 0.000000},
"plus": {GlyphName: "plus", Wx: 584.000000, Wy: 0.000000},
"plusminus": {GlyphName: "plusminus", Wx: 584.000000, Wy: 0.000000},
"q": {GlyphName: "q", Wx: 556.000000, Wy: 0.000000},
"question": {GlyphName: "question", Wx: 556.000000, Wy: 0.000000},
"questiondown": {GlyphName: "questiondown", Wx: 611.000000, Wy: 0.000000},
"quotedbl": {GlyphName: "quotedbl", Wx: 355.000000, Wy: 0.000000},
"quotedblbase": {GlyphName: "quotedblbase", Wx: 333.000000, Wy: 0.000000},
"quotedblleft": {GlyphName: "quotedblleft", Wx: 333.000000, Wy: 0.000000},
"quotedblright": {GlyphName: "quotedblright", Wx: 333.000000, Wy: 0.000000},
"quoteleft": {GlyphName: "quoteleft", Wx: 222.000000, Wy: 0.000000},
"quoteright": {GlyphName: "quoteright", Wx: 222.000000, Wy: 0.000000},
"quotesinglbase": {GlyphName: "quotesinglbase", Wx: 222.000000, Wy: 0.000000},
"quotesingle": {GlyphName: "quotesingle", Wx: 191.000000, Wy: 0.000000},
"r": {GlyphName: "r", Wx: 333.000000, Wy: 0.000000},
"racute": {GlyphName: "racute", Wx: 333.000000, Wy: 0.000000},
"radical": {GlyphName: "radical", Wx: 453.000000, Wy: 0.000000},
"rcaron": {GlyphName: "rcaron", Wx: 333.000000, Wy: 0.000000},
"rcommaaccent": {GlyphName: "rcommaaccent", Wx: 333.000000, Wy: 0.000000},
"registered": {GlyphName: "registered", Wx: 737.000000, Wy: 0.000000},
"ring": {GlyphName: "ring", Wx: 333.000000, Wy: 0.000000},
"s": {GlyphName: "s", Wx: 500.000000, Wy: 0.000000},
"sacute": {GlyphName: "sacute", Wx: 500.000000, Wy: 0.000000},
"scaron": {GlyphName: "scaron", Wx: 500.000000, Wy: 0.000000},
"scedilla": {GlyphName: "scedilla", Wx: 500.000000, Wy: 0.000000},
"scommaaccent": {GlyphName: "scommaaccent", Wx: 500.000000, Wy: 0.000000},
"section": {GlyphName: "section", Wx: 556.000000, Wy: 0.000000},
"semicolon": {GlyphName: "semicolon", Wx: 278.000000, Wy: 0.000000},
"seven": {GlyphName: "seven", Wx: 556.000000, Wy: 0.000000},
"six": {GlyphName: "six", Wx: 556.000000, Wy: 0.000000},
"slash": {GlyphName: "slash", Wx: 278.000000, Wy: 0.000000},
"space": {GlyphName: "space", Wx: 278.000000, Wy: 0.000000},
"sterling": {GlyphName: "sterling", Wx: 556.000000, Wy: 0.000000},
"summation": {GlyphName: "summation", Wx: 600.000000, Wy: 0.000000},
"t": {GlyphName: "t", Wx: 278.000000, Wy: 0.000000},
"tcaron": {GlyphName: "tcaron", Wx: 317.000000, Wy: 0.000000},
"tcommaaccent": {GlyphName: "tcommaaccent", Wx: 278.000000, Wy: 0.000000},
"thorn": {GlyphName: "thorn", Wx: 556.000000, Wy: 0.000000},
"three": {GlyphName: "three", Wx: 556.000000, Wy: 0.000000},
"threequarters": {GlyphName: "threequarters", Wx: 834.000000, Wy: 0.000000},
"threesuperior": {GlyphName: "threesuperior", Wx: 333.000000, Wy: 0.000000},
"tilde": {GlyphName: "tilde", Wx: 333.000000, Wy: 0.000000},
"trademark": {GlyphName: "trademark", Wx: 1000.000000, Wy: 0.000000},
"two": {GlyphName: "two", Wx: 556.000000, Wy: 0.000000},
"twosuperior": {GlyphName: "twosuperior", Wx: 333.000000, Wy: 0.000000},
"u": {GlyphName: "u", Wx: 556.000000, Wy: 0.000000},
"uacute": {GlyphName: "uacute", Wx: 556.000000, Wy: 0.000000},
"ucircumflex": {GlyphName: "ucircumflex", Wx: 556.000000, Wy: 0.000000},
"udieresis": {GlyphName: "udieresis", Wx: 556.000000, Wy: 0.000000},
"ugrave": {GlyphName: "ugrave", Wx: 556.000000, Wy: 0.000000},
"uhungarumlaut": {GlyphName: "uhungarumlaut", Wx: 556.000000, Wy: 0.000000},
"umacron": {GlyphName: "umacron", Wx: 556.000000, Wy: 0.000000},
"underscore": {GlyphName: "underscore", Wx: 556.000000, Wy: 0.000000},
"uogonek": {GlyphName: "uogonek", Wx: 556.000000, Wy: 0.000000},
"uring": {GlyphName: "uring", Wx: 556.000000, Wy: 0.000000},
"v": {GlyphName: "v", Wx: 500.000000, Wy: 0.000000},
"w": {GlyphName: "w", Wx: 722.000000, Wy: 0.000000},
"x": {GlyphName: "x", Wx: 500.000000, Wy: 0.000000},
"y": {GlyphName: "y", Wx: 500.000000, Wy: 0.000000},
"yacute": {GlyphName: "yacute", Wx: 500.000000, Wy: 0.000000},
"ydieresis": {GlyphName: "ydieresis", Wx: 500.000000, Wy: 0.000000},
"yen": {GlyphName: "yen", Wx: 556.000000, Wy: 0.000000},
"z": {GlyphName: "z", Wx: 500.000000, Wy: 0.000000},
"zacute": {GlyphName: "zacute", Wx: 500.000000, Wy: 0.000000},
"zcaron": {GlyphName: "zcaron", Wx: 500.000000, Wy: 0.000000},
"zdotaccent": {GlyphName: "zdotaccent", Wx: 500.000000, Wy: 0.000000},
"zero": {GlyphName: "zero", Wx: 556.000000, Wy: 0.000000},
}

View File

@ -124,7 +124,7 @@ func (this *Image) ToGoImage() (goimage.Image, error) {
b := uint16(samples[i+4])<<8 | uint16(samples[i+5])
a := uint16(0)
if this.alphaData != nil && len(this.alphaData) > aidx+1 {
a = uint16(this.alphaData[aidx]<<8) | uint16(this.alphaData[aidx+1])
a = (uint16(this.alphaData[aidx]) << 8) | uint16(this.alphaData[aidx+1])
aidx += 2
}
c = gocolor.RGBA64{R: r, G: g, B: b, A: a}

View File

@ -100,7 +100,7 @@ func newPdfOutlineFromIndirectObject(container *PdfIndirectObject) (*PdfOutline,
typeVal, ok := obj.(*PdfObjectName)
if ok {
if *typeVal != "Outlines" {
common.Log.Error("Type != Outlines (%s)", *typeVal)
common.Log.Debug("ERROR Type != Outlines (%s)", *typeVal)
// Should be "Outlines" if there, but some files have other types
// Log as an error but do not quit.
// Might be a good idea to log this kind of deviation from the standard separately.
@ -213,7 +213,7 @@ func (n *PdfOutlineTreeNode) getOuter() PdfModel {
return outlineItem
}
common.Log.Error("Invalid outline tree node item") // Should never happen.
common.Log.Debug("ERROR Invalid outline tree node item") // Should never happen.
return nil
}

View File

@ -400,10 +400,6 @@ func (this *PdfPage) GetMediaBox() (*PdfRectangle, error) {
return nil, errors.New("Media box not defined")
}
// GetResources returns the inheritable resources, either from the page or or a higher up page/pages struct.
func (this *PdfPage) GetResources() (*PdfPageResources, error) {
return this.getResources()
}
// Get the inheritable resources, either from the page or or a higher up page/pages struct.
func (this *PdfPage) getResources() (*PdfPageResources, error) {
@ -513,7 +509,7 @@ func (this *PdfPage) GetPageDict() *PdfObjectDictionary {
return p
}
// Get the page object as an indirect object. Wraps the Page
// Get the page object as an indirect objects. Wraps the Page
// dictionary into an indirect object.
func (this *PdfPage) GetPageAsIndirectObject() *PdfIndirectObject {
return this.primitive

View File

@ -32,8 +32,6 @@ type PdfReader struct {
traversed map[PdfObject]bool
}
// NewPdfReader returns a PdfReader
// the trailer, cross-reference table and "structure" are loaded.
func NewPdfReader(rs io.ReadSeeker) (*PdfReader, error) {
pdfReader := &PdfReader{}
pdfReader.traversed = map[PdfObject]bool{}
@ -149,7 +147,7 @@ func (this *PdfReader) loadStructure() error {
}
oc, err := this.parser.LookupByReference(*root)
if err != nil {
common.Log.Error("Failed to read root element catalog: %s", err)
common.Log.Debug("ERROR: Failed to read root element catalog: %s", err)
return err
}
pcatalog, ok := oc.(*PdfIndirectObject)
@ -159,7 +157,7 @@ func (this *PdfReader) loadStructure() error {
}
catalog, ok := (*pcatalog).PdfObject.(*PdfObjectDictionary)
if !ok {
common.Log.Error("Invalid catalog (%s)", pcatalog.PdfObject)
common.Log.Debug("ERROR: Invalid catalog (%s)", pcatalog.PdfObject)
return errors.New("Invalid catalog")
}
common.Log.Trace("Catalog: %s", catalog)
@ -171,23 +169,23 @@ func (this *PdfReader) loadStructure() error {
}
op, err := this.parser.LookupByReference(*pagesRef)
if err != nil {
common.Log.Error("Failed to read pages")
common.Log.Debug("ERROR: Failed to read pages")
return err
}
ppages, ok := op.(*PdfIndirectObject)
if !ok {
common.Log.Error("Pages object invalid")
common.Log.Error("op: %p", ppages)
common.Log.Debug("ERROR: Pages object invalid")
common.Log.Debug("op: %p", ppages)
return errors.New("Pages object invalid")
}
pages, ok := ppages.PdfObject.(*PdfObjectDictionary)
if !ok {
common.Log.Error("Pages object invalid (%s)", ppages)
common.Log.Debug("ERROR: Pages object invalid (%s)", ppages)
return errors.New("Pages object invalid")
}
pageCount, ok := pages.Get("Count").(*PdfObjectInteger)
if !ok {
common.Log.Error("Pages count object invalid")
common.Log.Debug("ERROR: Pages count object invalid")
return errors.New("Pages count invalid")
}
@ -210,7 +208,7 @@ func (this *PdfReader) loadStructure() error {
// Outlines.
this.outlineTree, err = this.loadOutlines()
if err != nil {
common.Log.Error("Failed to build outline tree (%s)", err)
common.Log.Debug("ERROR: Failed to build outline tree (%s)", err)
return err
}
@ -272,7 +270,7 @@ func (this *PdfReader) loadOutlines() (*PdfOutlineTreeNode, error) {
// Trace references to the object.
outlineRootObj, err := this.traceToObject(outlinesObj)
if err != nil {
common.Log.Error("Failed to read outlines")
common.Log.Debug("ERROR: Failed to read outlines")
return nil, err
}
common.Log.Trace("Outline root: %v", outlineRootObj)
@ -424,7 +422,7 @@ func (this *PdfReader) GetOutlinesFlattened() ([]*PdfOutlineTreeNode, []string,
return
}
if node.context == nil {
common.Log.Error("Missing node.context") // Should not happen ever.
common.Log.Debug("ERROR: Missing node.context") // Should not happen ever.
return
}
@ -483,7 +481,7 @@ func (this *PdfReader) loadForms() (*PdfAcroForm, error) {
common.Log.Trace("Traverse the Acroforms structure")
err = this.traverseObjectData(formsDict)
if err != nil {
common.Log.Error("Unable to traverse AcroForms (%s)", err)
common.Log.Debug("ERROR: Unable to traverse AcroForms (%s)", err)
return nil, err
}
@ -543,7 +541,7 @@ func (this *PdfReader) buildPageList(node *PdfIndirectObject, parent *PdfIndirec
return nil
}
if *objType != "Pages" {
common.Log.Error("Table of content containing non Page/Pages object! (%s)", objType)
common.Log.Debug("ERROR: Table of content containing non Page/Pages object! (%s)", objType)
return errors.New("Table of content containing non Page/Pages object!")
}
@ -560,7 +558,7 @@ func (this *PdfReader) buildPageList(node *PdfIndirectObject, parent *PdfIndirec
kidsObj, err := this.parser.Trace(nodeDict.Get("Kids"))
if err != nil {
common.Log.Error("Failed loading Kids object")
common.Log.Debug("ERROR: Failed loading Kids object")
return err
}
@ -580,7 +578,7 @@ func (this *PdfReader) buildPageList(node *PdfIndirectObject, parent *PdfIndirec
for idx, child := range *kids {
child, ok := child.(*PdfIndirectObject)
if !ok {
common.Log.Error("Page not indirect object - (%s)", child)
common.Log.Debug("ERROR: Page not indirect object - (%s)", child)
return errors.New("Page not indirect object")
}
(*kids)[idx] = child
@ -692,7 +690,7 @@ func (this *PdfReader) traverseObjectData(o PdfObject) error {
}
if _, isRef := o.(*PdfObjectReference); isRef {
common.Log.Error("Reader tracing a reference!")
common.Log.Debug("ERROR: Reader tracing a reference!")
return errors.New("Reader tracing a reference!")
}
@ -763,6 +761,15 @@ func (this *PdfReader) Inspect() (map[string]int, error) {
return this.parser.Inspect()
}
// GetObjectNums returns the object numbers of the PDF objects in the file
// Numbered objects are either indirect objects or stream objects.
// e.g. objNums := pdfReader.GetObjectNums()
// The underlying objects can then be accessed with
// pdfReader.GetIndirectObjectByNumber(objNums[0]) for the first available object.
func (r *PdfReader) GetObjectNums() []int {
return r.parser.GetObjectNums()
}
// Get specific object number.
func (this *PdfReader) GetIndirectObjectByNumber(number int) (PdfObject, error) {
obj, err := this.parser.LookupByNumber(number)

View File

@ -138,7 +138,7 @@ func (r *PdfPageResources) GetShadingByName(keyName PdfObjectName) (*PdfShading,
return nil, false
}
shadingDict, ok := r.Shading.(*PdfObjectDictionary)
shadingDict, ok := TraceToDirectObject(r.Shading).(*PdfObjectDictionary)
if !ok {
common.Log.Debug("ERROR: Invalid Shading entry - not a dict (got %T)", r.Shading)
return nil, false
@ -178,7 +178,7 @@ func (r *PdfPageResources) GetPatternByName(keyName PdfObjectName) (*PdfPattern,
return nil, false
}
patternDict, ok := r.Pattern.(*PdfObjectDictionary)
patternDict, ok := TraceToDirectObject(r.Pattern).(*PdfObjectDictionary)
if !ok {
common.Log.Debug("ERROR: Invalid Pattern entry - not a dict (got %T)", r.Pattern)
return nil, false
@ -326,7 +326,7 @@ func (r *PdfPageResources) GetXObjectByName(keyName PdfObjectName) (*PdfObjectSt
}
dict := stream.PdfObjectDictionary
name, ok := dict.Get("Subtype").(*PdfObjectName)
name, ok := TraceToDirectObject(dict.Get("Subtype")).(*PdfObjectName)
if !ok {
common.Log.Debug("XObject Subtype not a Name, dict: %s", dict.String())
return nil, XObjectTypeUndefined

View File

@ -1,3 +1,5 @@
// +build unidev
package main
// Utility to generate static maps of glyph <-> character codes for text encoding.

View File

@ -1,3 +1,5 @@
// +build unidev
package main
// Utility to generate static maps of glyph <-> rune conversions for a glyphlist.
@ -56,8 +58,8 @@ func main() {
func printGlyphToRuneList(glyphToUnicodeMap map[string]string) {
keys := []string{}
for k, _ := range glyphToUnicodeMap {
keys = append(keys, k)
for key := range glyphToUnicodeMap {
keys = append(keys, key)
}
sort.Strings(keys)
@ -71,8 +73,8 @@ func printGlyphToRuneList(glyphToUnicodeMap map[string]string) {
func printRuneToGlyphList(glyphToUnicodeMap map[string]string) {
keys := []string{}
for k, _ := range glyphToUnicodeMap {
keys = append(keys, k)
for key := range glyphToUnicodeMap {
keys = append(keys, key)
}
sort.Strings(keys)

View File

@ -157,8 +157,9 @@ func (this *PdfWriter) hasObject(obj PdfObject) bool {
return false
}
// addObject adds `obj` to list of objects.
// Returns true if `obj` was not already in list of objects, or false it was.
// Adds the object to list of objects and returns true if the obj was
// not already added.
// Returns false if the object was previously added.
func (this *PdfWriter) addObject(obj PdfObject) bool {
hasObj := this.hasObject(obj)
if !hasObj {
@ -169,9 +170,6 @@ func (this *PdfWriter) addObject(obj PdfObject) bool {
return false
}
// addObjects recursively adds `obj` to the list of objects.
// If `obj` is a container then its elements are added with a recursive call to addObjects.
// In `obj` is a PdfIndirectObject or PdfObjectStream, its contents are added after it is added.
func (this *PdfWriter) addObjects(obj PdfObject) error {
common.Log.Trace("Adding objects!")
@ -228,7 +226,7 @@ func (this *PdfWriter) addObjects(obj PdfObject) error {
// Could refer to somewhere outside of the scope of the output doc.
// Should be done by the reader already.
// -> ERROR.
common.Log.Error("Parent is a reference object - Cannot be in writer (needs to be resolved)")
common.Log.Debug("ERROR: Parent is a reference object - Cannot be in writer (needs to be resolved)")
return fmt.Errorf("Parent is a reference object - Cannot be in writer (needs to be resolved) - %s", parentObj)
}
}
@ -253,14 +251,14 @@ func (this *PdfWriter) addObjects(obj PdfObject) error {
if _, isReference := obj.(*PdfObjectReference); isReference {
// Should never be a reference, should already be resolved.
common.Log.Error("Cannot be a reference!")
common.Log.Debug("ERROR: Cannot be a reference!")
return errors.New("Reference not allowed")
}
return nil
}
// AddPage adds a page to the PDF file. The new page should be an indirect
// Add a page to the PDF file. The new page should be an indirect
// object.
func (this *PdfWriter) AddPage(page *PdfPage) error {
obj := page.ToPdfObject()
@ -269,14 +267,14 @@ func (this *PdfWriter) AddPage(page *PdfPage) error {
pageObj, ok := obj.(*PdfIndirectObject)
if !ok {
return errors.New("Page should be an indirect object.")
return errors.New("Page should be an indirect object")
}
common.Log.Trace("%s", pageObj)
common.Log.Trace("%s", pageObj.PdfObject)
pDict, ok := pageObj.PdfObject.(*PdfObjectDictionary)
if !ok {
return errors.New("Page object should be a dictionary.")
return errors.New("Page object should be a dictionary")
}
otype, ok := pDict.Get("Type").(*PdfObjectName)
@ -517,7 +515,7 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio
return nil
}
// Write out the pdf.
// Write the pdf out.
func (this *PdfWriter) Write(ws io.WriteSeeker) error {
common.Log.Trace("Write()")
// Outlines.

View File

@ -306,7 +306,7 @@ func NewXObjectImageFromStream(stream *PdfObjectStream) (*XObjectImage, error) {
}
img.Filter = encoder
if obj := dict.Get("Width"); obj != nil {
if obj := TraceToDirectObject(dict.Get("Width")); obj != nil {
iObj, ok := obj.(*PdfObjectInteger)
if !ok {
return nil, errors.New("Invalid image width object")
@ -317,7 +317,7 @@ func NewXObjectImageFromStream(stream *PdfObjectStream) (*XObjectImage, error) {
return nil, errors.New("Width missing")
}
if obj := dict.Get("Height"); obj != nil {
if obj := TraceToDirectObject(dict.Get("Height")); obj != nil {
iObj, ok := obj.(*PdfObjectInteger)
if !ok {
return nil, errors.New("Invalid image height object")
@ -328,7 +328,7 @@ func NewXObjectImageFromStream(stream *PdfObjectStream) (*XObjectImage, error) {
return nil, errors.New("Height missing")
}
if obj := dict.Get("ColorSpace"); obj != nil {
if obj := TraceToDirectObject(dict.Get("ColorSpace")); obj != nil {
cs, err := newPdfColorspaceFromPdfObject(obj)
if err != nil {
return nil, err
@ -340,7 +340,7 @@ func NewXObjectImageFromStream(stream *PdfObjectStream) (*XObjectImage, error) {
img.ColorSpace = NewPdfColorspaceDeviceGray()
}
if obj := dict.Get("BitsPerComponent"); obj != nil {
if obj := TraceToDirectObject(dict.Get("BitsPerComponent")); obj != nil {
iObj, ok := obj.(*PdfObjectInteger)
if !ok {
return nil, errors.New("Invalid image height object")

View File

@ -9,6 +9,8 @@ package ps
import (
"fmt"
"github.com/unidoc/unidoc/common"
)
// A PSExecutor has its own execution stack and is used to executre a PS routine (program).
@ -52,6 +54,7 @@ func (this *PSExecutor) Execute(objects []PSObject) ([]PSObject, error) {
err := this.program.Exec(this.Stack)
if err != nil {
common.Log.Debug("Exec failed: %v", err)
return nil, err
}

View File

@ -587,6 +587,12 @@ func (this *PSOperand) Dup(stack *PSStack) error {
return err
}
// Push it back.
err = stack.Push(obj)
if err != nil {
return err
}
// Push the duplicate.
err = stack.Push(obj.Duplicate())
return err
}
@ -1217,7 +1223,7 @@ func (this *PSOperand) Roll(stack *PSStack) error {
} else {
// if j < 0: put the bottom element on top
bottom := substack[len(substack)-n.Val]
substack = append(substack[1:len(substack)], bottom)
substack = append(substack[1:], bottom)
}
s := append((*stack)[0:len(*stack)-n.Val], substack...)

View File

@ -16,7 +16,8 @@ import (
)
func init() {
common.SetLogger(common.ConsoleLogger{})
common.SetLogger(common.NewConsoleLogger(common.LogLevelDebug))
//common.SetLogger(common.NewConsoleLogger(common.LogLevelTrace))
}
func makeReaderForText(txt string) *bufio.Reader {
@ -270,6 +271,8 @@ func TestFunctionOperations(t *testing.T) {
func TestVariousCases(t *testing.T) {
testcases := []ComplexTestEntry{
// dup
{progText: "{ 99 dup }", expected: "[ int:99 int:99 ]"},
// ceiling
{progText: "{ 3.2 ceiling }", expected: "[ real:4.00000 ]"},
{progText: "{ -4.8 ceiling }", expected: "[ real:-4.00000 ]"},
@ -405,3 +408,24 @@ func TestVariousCases(t *testing.T) {
}
}
}
func TestTintTransform1(t *testing.T) {
testcases := []ComplexTestEntry{
// from corpus epson_pages3_color_pages1.pdf.
{progText: "{ 0.0000 dup 0 mul exch dup 0 mul exch dup 0 mul exch 1 mul }", expected: "[ real:0.00000 real:0.00000 real:0.00000 real:0.00000 ]"},
}
for _, testcase := range testcases {
stack, err := quickTest(testcase.progText)
if err != nil {
t.Errorf("Error: %v", err)
return
}
// Maybe not the most robust test (comparing the strings), but should do.
if stack.DebugString() != testcase.expected {
t.Errorf("Wrong result: '%s' != '%s'", stack.DebugString(), testcase.expected)
return
}
}
}

View File

@ -0,0 +1 @@
Copyright 2011 Google Inc. All Rights Reserved.

View File

@ -0,0 +1,13 @@
<p>
Roboto has a dual nature.
It has a mechanical skeleton and the forms are largely geometric.
At the same time, the font features friendly and open curves.
While some grotesks distort their letterforms to force a rigid rhythm, Roboto doesnt compromise, allowing letters to be settled into their natural width.
This makes for a more natural reading rhythm more commonly found in humanist and serif types.
</p>
<p>
This is the regular family, which can be used alongside the <a href="http://www.google.com/fonts/specimen/Roboto+Condensed">Roboto Condensed</a> family and the <a href="http://www.google.com/fonts/specimen/Roboto+Slab">Roboto Slab</a> family.
</p>
<p>
To contribute, see <a href="https://github.com/google/roboto/">github.com/google/roboto</a>
</p>

View File

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

View File

@ -0,0 +1,121 @@
name: "Roboto"
designer: "Christian Robertson"
license: "APACHE2"
category: "SANS_SERIF"
date_added: "2013-01-09"
fonts {
name: "Roboto"
style: "normal"
weight: 100
filename: "Roboto-Thin.ttf"
post_script_name: "Roboto-Thin"
full_name: "Roboto Thin"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "italic"
weight: 100
filename: "Roboto-ThinItalic.ttf"
post_script_name: "Roboto-ThinItalic"
full_name: "Roboto Thin Italic"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "normal"
weight: 300
filename: "Roboto-Light.ttf"
post_script_name: "Roboto-Light"
full_name: "Roboto Light"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "italic"
weight: 300
filename: "Roboto-LightItalic.ttf"
post_script_name: "Roboto-LightItalic"
full_name: "Roboto Light Italic"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "normal"
weight: 400
filename: "Roboto-Regular.ttf"
post_script_name: "Roboto-Regular"
full_name: "Roboto"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "italic"
weight: 400
filename: "Roboto-Italic.ttf"
post_script_name: "Roboto-Italic"
full_name: "Roboto Italic"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "normal"
weight: 500
filename: "Roboto-Medium.ttf"
post_script_name: "Roboto-Medium"
full_name: "Roboto Medium"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "italic"
weight: 500
filename: "Roboto-MediumItalic.ttf"
post_script_name: "Roboto-MediumItalic"
full_name: "Roboto Medium Italic"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "normal"
weight: 700
filename: "Roboto-Bold.ttf"
post_script_name: "Roboto-Bold"
full_name: "Roboto Bold"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "italic"
weight: 700
filename: "Roboto-BoldItalic.ttf"
post_script_name: "Roboto-BoldItalic"
full_name: "Roboto Bold Italic"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "normal"
weight: 900
filename: "Roboto-Black.ttf"
post_script_name: "Roboto-Black"
full_name: "Roboto Black"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
fonts {
name: "Roboto"
style: "italic"
weight: 900
filename: "Roboto-BlackItalic.ttf"
post_script_name: "Roboto-BlackItalic"
full_name: "Roboto Black Italic"
copyright: "Copyright 2011 Google Inc. All Rights Reserved."
}
subsets: "menu"
subsets: "cyrillic"
subsets: "cyrillic-ext"
subsets: "greek"
subsets: "greek-ext"
subsets: "latin"
subsets: "latin-ext"
subsets: "vietnamese"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -30,16 +30,18 @@ build:
- script:
name: go get
code: |
go get
cd $WERCKER_SOURCE_DIR
go version
go get -t $(go list ./... | grep -v textencoding/glyphlist)
# Build the project
- script:
name: go build
code: |
go build $(go list ./... | grep -v examples)
go build $(go list ./... | grep -v examples | grep -v textencoding/glyphlist)
# Test the project
- script:
name: go test
code: |
go test $(go list ./... | grep -v examples)
go test $(go list ./... | grep -v examples| grep -v textencoding/glyphlist)