package model import ( "errors" "fmt" "gitea.tecamino.com/paadi/pdfmerge/internal/pdf/common" "gitea.tecamino.com/paadi/pdfmerge/internal/pdf/core" ) // A PdfPattern can represent a Pattern, either a tiling pattern or a shading pattern. // Note that all patterns shall be treated as colours; a Pattern colour space shall be established with the CS or cs // operator just like other colour spaces, and a particular pattern shall be installed as the current colour with the // SCN or scn operator. type PdfPattern struct { // Type: Pattern PatternType int64 context PdfModel // The sub pattern, either PdfTilingPattern (Type 1) or PdfShadingPattern (Type 2). container core.PdfObject } func (pp *PdfPattern) GetContainingPdfObject() core.PdfObject { return pp.container } // Context in this case is a reference to the subpattern entry: either PdfTilingPattern or PdfShadingPattern. func (pp *PdfPattern) GetContext() PdfModel { return pp.context } // Set the sub pattern (context). Either PdfTilingPattern or PdfShadingPattern. func (pp *PdfPattern) SetContext(ctx PdfModel) { pp.context = ctx } func (pp *PdfPattern) IsTiling() bool { return pp.PatternType == 1 } func (pp *PdfPattern) IsShading() bool { return pp.PatternType == 2 } // Check with IsTiling() prior to using this to ensure is a tiling pattern. func (pp *PdfPattern) GetAsTilingPattern() *PdfTilingPattern { return pp.context.(*PdfTilingPattern) } // Check with IsShading() prior to using this, to ensure is a shading pattern. func (pp *PdfPattern) GetAsShadingPattern() *PdfShadingPattern { return pp.context.(*PdfShadingPattern) } // A Tiling pattern consists of repetitions of a pattern cell with defined intervals. // It is a type 1 pattern. (PatternType = 1). // A tiling pattern is represented by a stream object, where the stream content is // a content stream that describes the pattern cell. type PdfTilingPattern struct { *PdfPattern PaintType *core.PdfObjectInteger // Colored or uncolored tiling pattern. TilingType *core.PdfObjectInteger // Constant spacing, no distortion or constant spacing/faster tiling. BBox *PdfRectangle XStep *core.PdfObjectFloat YStep *core.PdfObjectFloat Resources *PdfPageResources Matrix *core.PdfObjectArray // Pattern matrix (6 numbers). } func (ptp *PdfTilingPattern) IsColored() bool { if ptp.PaintType != nil && *ptp.PaintType == 1 { return true } else { return false } } // GetContentStream returns the pattern cell's content stream func (ptp *PdfTilingPattern) GetContentStream() ([]byte, error) { decoded, _, err := ptp.GetContentStreamWithEncoder() return decoded, err } // GetContentStreamWithEncoder returns the pattern cell's content stream and its encoder // TODO (v3): Change GetContentStreamWithEncoder to GetContentStream func (ptp *PdfTilingPattern) GetContentStreamWithEncoder() ([]byte, core.StreamEncoder, error) { streamObj, ok := ptp.container.(*core.PdfObjectStream) if !ok { common.Log.Debug("Tiling pattern container not a stream (got %T)", ptp.container) return nil, nil, ErrTypeError } decoded, err := core.DecodeStream(streamObj) if err != nil { common.Log.Debug("Failed decoding stream, err: %v", err) return nil, nil, err } encoder, err := core.NewEncoderFromStream(streamObj) if err != nil { common.Log.Debug("Failed finding decoding encoder: %v", err) return nil, nil, err } return decoded, encoder, nil } // Set the pattern cell's content stream. func (ptp *PdfTilingPattern) SetContentStream(content []byte, encoder core.StreamEncoder) error { streamObj, ok := ptp.container.(*core.PdfObjectStream) if !ok { common.Log.Debug("Tiling pattern container not a stream (got %T)", ptp.container) return ErrTypeError } // If encoding is not set, use raw encoder. if encoder == nil { encoder = core.NewRawEncoder() } streamDict := streamObj.PdfObjectDictionary // Make a new stream dict based on the encoding parameters. encDict := encoder.MakeStreamDict() // Merge the encoding dict into the stream dict. streamDict.Merge(encDict) encoded, err := encoder.EncodeBytes(content) if err != nil { return err } // Update length. streamDict.Set("Length", core.MakeInteger(int64(len(encoded)))) streamObj.Stream = []byte(encoded) return nil } // Shading patterns provide a smooth transition between colors across an area to be painted, i.e. // color(x,y) = f(x,y) at each point. // It is a type 2 pattern (PatternType = 2). type PdfShadingPattern struct { *PdfPattern Shading *PdfShading Matrix *core.PdfObjectArray ExtGState core.PdfObject } // Load a pdf pattern from an indirect object. Used in parsing/loading PDFs. func newPdfPatternFromPdfObject(container core.PdfObject) (*PdfPattern, error) { pattern := &PdfPattern{} var dict *core.PdfObjectDictionary if indObj, is := container.(*core.PdfIndirectObject); is { pattern.container = indObj d, ok := indObj.PdfObject.(*core.PdfObjectDictionary) if !ok { common.Log.Debug("Pattern indirect object not containing dictionary (got %T)", indObj.PdfObject) return nil, ErrTypeError } dict = d } else if streamObj, is := container.(*core.PdfObjectStream); is { pattern.container = streamObj dict = streamObj.PdfObjectDictionary } else { common.Log.Debug("Pattern not an indirect object or stream") return nil, ErrTypeError } // PatternType. obj := dict.Get("PatternType") if obj == nil { common.Log.Debug("Pdf Pattern not containing PatternType") return nil, ErrRequiredAttributeMissing } patternType, ok := obj.(*core.PdfObjectInteger) if !ok { common.Log.Debug("Pattern type not an integer (got %T)", obj) return nil, ErrTypeError } if *patternType != 1 && *patternType != 2 { common.Log.Debug("Pattern type != 1/2 (got %d)", *patternType) return nil, ErrRangeError } pattern.PatternType = int64(*patternType) switch *patternType { case 1: // Tiling pattern. ctx, err := newPdfTilingPatternFromDictionary(dict) if err != nil { return nil, err } ctx.PdfPattern = pattern pattern.context = ctx return pattern, nil case 2: // Shading pattern. ctx, err := newPdfShadingPatternFromDictionary(dict) if err != nil { return nil, err } ctx.PdfPattern = pattern pattern.context = ctx return pattern, nil } return nil, errors.New("unknown pattern") } // Load entries specific to a pdf tiling pattern from a dictionary. Used in parsing/loading PDFs. func newPdfTilingPatternFromDictionary(dict *core.PdfObjectDictionary) (*PdfTilingPattern, error) { pattern := &PdfTilingPattern{} // PaintType (required). obj := dict.Get("PaintType") if obj == nil { common.Log.Debug("PaintType missing") return nil, ErrRequiredAttributeMissing } paintType, ok := obj.(*core.PdfObjectInteger) if !ok { common.Log.Debug("PaintType not an integer (got %T)", obj) return nil, ErrTypeError } pattern.PaintType = paintType // TilingType (required). obj = dict.Get("TilingType") if obj == nil { common.Log.Debug("TilingType missing") return nil, ErrRequiredAttributeMissing } tilingType, ok := obj.(*core.PdfObjectInteger) if !ok { common.Log.Debug("TilingType not an integer (got %T)", obj) return nil, ErrTypeError } pattern.TilingType = tilingType // BBox (required). obj = dict.Get("BBox") if obj == nil { common.Log.Debug("BBox missing") return nil, ErrRequiredAttributeMissing } obj = core.TraceToDirectObject(obj) arr, ok := obj.(*core.PdfObjectArray) if !ok { common.Log.Debug("BBox should be specified by an array (got %T)", obj) return nil, ErrTypeError } rect, err := NewPdfRectangle(*arr) if err != nil { common.Log.Debug("BBox error: %v", err) return nil, err } pattern.BBox = rect // XStep (required). obj = dict.Get("XStep") if obj == nil { common.Log.Debug("XStep missing") return nil, ErrRequiredAttributeMissing } xStep, err := getNumberAsFloat(obj) if err != nil { common.Log.Debug("error getting XStep as float: %v", xStep) return nil, err } pattern.XStep = core.MakeFloat(xStep) // YStep (required). obj = dict.Get("YStep") if obj == nil { common.Log.Debug("YStep missing") return nil, ErrRequiredAttributeMissing } yStep, err := getNumberAsFloat(obj) if err != nil { common.Log.Debug("error getting YStep as float: %v", yStep) return nil, err } pattern.YStep = core.MakeFloat(yStep) // Resources (required). obj = dict.Get("Resources") if obj == nil { common.Log.Debug("Resources missing") return nil, ErrRequiredAttributeMissing } dict, ok = core.TraceToDirectObject(obj).(*core.PdfObjectDictionary) if !ok { return nil, fmt.Errorf("invalid resource dictionary (%T)", obj) } resources, err := NewPdfPageResourcesFromDict(dict) if err != nil { return nil, err } pattern.Resources = resources // Matrix (optional). if obj := dict.Get("Matrix"); obj != nil { arr, ok := obj.(*core.PdfObjectArray) if !ok { common.Log.Debug("Matrix not an array (got %T)", obj) return nil, ErrTypeError } pattern.Matrix = arr } return pattern, nil } // Load entries specific to a pdf shading pattern from a dictionary. Used in parsing/loading PDFs. func newPdfShadingPatternFromDictionary(dict *core.PdfObjectDictionary) (*PdfShadingPattern, error) { pattern := &PdfShadingPattern{} // Shading (required). obj := dict.Get("Shading") if obj == nil { common.Log.Debug("Shading missing") return nil, ErrRequiredAttributeMissing } shading, err := newPdfShadingFromPdfObject(obj) if err != nil { common.Log.Debug("error loading shading: %v", err) return nil, err } pattern.Shading = shading // Matrix (optional). if obj := dict.Get("Matrix"); obj != nil { arr, ok := obj.(*core.PdfObjectArray) if !ok { common.Log.Debug("Matrix not an array (got %T)", obj) return nil, ErrTypeError } pattern.Matrix = arr } // ExtGState (optional). if obj := dict.Get("ExtGState"); obj != nil { pattern.ExtGState = obj } return pattern, nil } /* Conversions to pdf objects. */ func (pp *PdfPattern) getDict() *core.PdfObjectDictionary { if indObj, is := pp.container.(*core.PdfIndirectObject); is { dict, ok := indObj.PdfObject.(*core.PdfObjectDictionary) if !ok { return nil } return dict } else if streamObj, is := pp.container.(*core.PdfObjectStream); is { return streamObj.PdfObjectDictionary } else { common.Log.Debug("Trying to access pattern dictionary of invalid object type (%T)", pp.container) return nil } } func (pp *PdfPattern) ToPdfObject() core.PdfObject { d := pp.getDict() d.Set("Type", core.MakeName("Pattern")) d.Set("PatternType", core.MakeInteger(pp.PatternType)) return pp.container } func (pt *PdfTilingPattern) ToPdfObject() core.PdfObject { pt.PdfPattern.ToPdfObject() d := pt.getDict() if pt.PaintType != nil { d.Set("PaintType", pt.PaintType) } if pt.TilingType != nil { d.Set("TilingType", pt.TilingType) } if pt.BBox != nil { d.Set("BBox", pt.BBox.ToPdfObject()) } if pt.XStep != nil { d.Set("XStep", pt.XStep) } if pt.YStep != nil { d.Set("YStep", pt.YStep) } if pt.Resources != nil { d.Set("Resources", pt.Resources.ToPdfObject()) } if pt.Matrix != nil { d.Set("Matrix", pt.Matrix) } return pt.container } func (psp *PdfShadingPattern) ToPdfObject() core.PdfObject { psp.PdfPattern.ToPdfObject() d := psp.getDict() if psp.Shading != nil { d.Set("Shading", psp.Shading.ToPdfObject()) } if psp.Matrix != nil { d.Set("Matrix", psp.Matrix) } if psp.ExtGState != nil { d.Set("ExtGState", psp.ExtGState) } return psp.container }