From 7751c17f106d9d7d4c6c290b969ece1c8dab4371 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 12 Nov 2017 14:04:27 -0500 Subject: Add partial support for spot colors. This does not yet include gradients, etc --- def.go | 31 +++++++++- fpdf.go | 35 ++++++------ fpdf_test.go | 14 +++++ spotcolor.go | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+), 17 deletions(-) create mode 100644 spotcolor.go diff --git a/def.go b/def.go index fd6ef81..88e4dea 100644 --- a/def.go +++ b/def.go @@ -39,6 +39,34 @@ type gradientType struct { objNum int } +type colorMode int + +const ( + colorModeRGB colorMode = iota + colorModeSpot + colorModeCMYK +) + +type colorType struct { + r, g, b float64 + ir, ig, ib int + mode colorMode + spotStr string // name of current spot color + gray bool + str string +} + +// SpotColorType specifies a named spot color value +type spotColorType struct { + id, objID int + val cmykColorType +} + +// CMYKColorType specifies an ink-based CMYK color value +type cmykColorType struct { + c, m, y, k byte // 0% to 100% +} + // SizeType fields Wd and Ht specify the horizontal and vertical extents of a // document element such as a page. type SizeType struct { @@ -254,8 +282,9 @@ type Fpdf struct { colorFlag bool // indicates whether fill and text colors are different color struct { // Composite values of colors - draw, fill, text clrType + draw, fill, text colorType } + spotColorMap map[string]spotColorType // Map of named ink-based colors } type encType struct { diff --git a/fpdf.go b/fpdf.go index 3b2d488..a590581 100644 --- a/fpdf.go +++ b/fpdf.go @@ -180,6 +180,7 @@ func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType) } // Enable compression f.SetCompression(!gl.noCompress) + f.spotColorMap = make(map[string]spotColorType) f.blendList = make([]blendModeType, 0, 8) f.blendList = append(f.blendList, blendModeType{}) // blendList[0] is unused (1-based) f.blendMap = make(map[string]int) @@ -721,13 +722,6 @@ func (f *Fpdf) PageNo() int { return f.page } -type clrType struct { - r, g, b float64 - ir, ig, ib int - gray bool - str string -} - func colorComp(v int) (int, float64) { if v < 0 { v = 0 @@ -737,10 +731,11 @@ func colorComp(v int) (int, float64) { return v, float64(v) / 255.0 } -func colorValue(r, g, b int, grayStr, fullStr string) (clr clrType) { +func rgbColorValue(r, g, b int, grayStr, fullStr string) (clr colorType) { clr.ir, clr.r = colorComp(r) clr.ig, clr.g = colorComp(g) clr.ib, clr.b = colorComp(b) + clr.mode = colorModeRGB clr.gray = clr.ir == clr.ig && clr.r == clr.b if len(grayStr) > 0 { if clr.gray { @@ -759,13 +754,15 @@ func colorValue(r, g, b int, grayStr, fullStr string) (clr clrType) { // The method can be called before the first page is created. The value is // retained from page to page. func (f *Fpdf) SetDrawColor(r, g, b int) { - f.color.draw = colorValue(r, g, b, "G", "RG") + f.color.draw = rgbColorValue(r, g, b, "G", "RG") if f.page > 0 { f.out(f.color.draw.str) } } -// GetDrawColor returns the current draw color as RGB components (0 - 255). +// GetDrawColor returns the most recently set draw color as RGB components (0 - +// 255). This will not be the current value if a draw color of some other type +// (for example, spot) has been more recently set. func (f *Fpdf) GetDrawColor() (int, int, int) { return f.color.draw.ir, f.color.draw.ig, f.color.draw.ib } @@ -775,14 +772,16 @@ func (f *Fpdf) GetDrawColor() (int, int, int) { // -255). The method can be called before the first page is created and the // value is retained from page to page. func (f *Fpdf) SetFillColor(r, g, b int) { - f.color.fill = colorValue(r, g, b, "g", "rg") + f.color.fill = rgbColorValue(r, g, b, "g", "rg") f.colorFlag = f.color.fill.str != f.color.text.str if f.page > 0 { f.out(f.color.fill.str) } } -// GetFillColor returns the current fill color as RGB components (0 - 255). +// GetFillColor returns the most recently set fill color as RGB components (0 - +// 255). This will not be the current value if a fill color of some other type +// (for example, spot) has been more recently set. func (f *Fpdf) GetFillColor() (int, int, int) { return f.color.fill.ir, f.color.fill.ig, f.color.fill.ib } @@ -791,11 +790,13 @@ func (f *Fpdf) GetFillColor() (int, int, int) { // components (0 - 255). The method can be called before the first page is // created. The value is retained from page to page. func (f *Fpdf) SetTextColor(r, g, b int) { - f.color.text = colorValue(r, g, b, "g", "rg") + f.color.text = rgbColorValue(r, g, b, "g", "rg") f.colorFlag = f.color.fill.str != f.color.text.str } -// GetTextColor returns the current text color as RGB components (0 - 255). +// GetTextColor returns the most recently set text color as RGB components (0 - +// 255). This will not be the current value if a text color of some other type +// (for example, spot) has been more recently set. func (f *Fpdf) GetTextColor() (int, int, int) { return f.color.text.ir, f.color.text.ig, f.color.text.ib } @@ -1183,8 +1184,8 @@ func (f *Fpdf) gradientClipEnd() { func (f *Fpdf) gradient(tp int, r1, g1, b1 int, r2, g2, b2 int, x1, y1 float64, x2, y2 float64, r float64) { pos := len(f.gradientList) - clr1 := colorValue(r1, g1, b1, "", "") - clr2 := colorValue(r2, g2, b2, "", "") + clr1 := rgbColorValue(r1, g1, b1, "", "") + clr2 := rgbColorValue(r2, g2, b2, "", "") f.gradientList = append(f.gradientList, gradientType{tp, clr1.str, clr2.str, x1, y1, x2, y2, r, 0}) f.outf("/Sh%d sh", pos) @@ -3477,6 +3478,7 @@ func (f *Fpdf) putresourcedict() { } // Layers f.layerPutResourceDict() + f.spotColorPutResourceDict() } func (f *Fpdf) putBlendModes() { @@ -3542,6 +3544,7 @@ func (f *Fpdf) putresources() { f.layerPutLayers() f.putBlendModes() f.putGradients() + f.putSpotColors() f.putfonts() if f.err != nil { return diff --git a/fpdf_test.go b/fpdf_test.go index db46528..76ef558 100644 --- a/fpdf_test.go +++ b/fpdf_test.go @@ -2046,3 +2046,17 @@ func ExampleFpdf_SetJavascript() { // Output: // Successfully generated pdf/Fpdf_SetJavascript.pdf } + +// This example demonstrates spot color use +func ExampleFpdf_AddSpotColor() { + pdf := gofpdf.New("P", "mm", "A4", "") + pdf.AddSpotColor("PANTONE 145 CVC", 0, 42, 100, 25) + pdf.AddPage() + pdf.SetFillSpotColor("PANTONE 145 CVC", 90) + pdf.Rect(80, 40, 50, 50, "F") + fileStr := example.Filename("Fpdf_AddSpotColor") + err := pdf.OutputFileAndClose(fileStr) + example.Summary(err, fileStr) + // Output: + // Successfully generated pdf/Fpdf_AddSpotColor.pdf +} diff --git a/spotcolor.go b/spotcolor.go new file mode 100644 index 0000000..33aca82 --- /dev/null +++ b/spotcolor.go @@ -0,0 +1,184 @@ +// Copyright (c) Kurt Jung (Gmail: kurt.w.jung) +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// Adapted from http://www.fpdf.org/en/script/script89.php by Olivier PLATHEY + +package gofpdf + +import ( + "fmt" + "strings" +) + +func byteBound(v byte) byte { + if v > 100 { + return 100 + } + return v +} + +// AddSpotColor adds an ink-based CMYK color to the gofpdf instance and +// associates it with the specified name. The individual components specify +// percentages ranging from 0 to 100. Values above this are quietly capped to +// 100. An error occurs if the specified name is already associated with a +// color. +func (f *Fpdf) AddSpotColor(nameStr string, c, m, y, k byte) { + if f.err == nil { + _, ok := f.spotColorMap[nameStr] + if !ok { + id := len(f.spotColorMap) + 1 + f.spotColorMap[nameStr] = spotColorType{ + id: id, + val: cmykColorType{ + c: byteBound(c), + m: byteBound(m), + y: byteBound(y), + k: byteBound(k), + }, + } + } else { + f.err = fmt.Errorf("name \"%s\" is already associated with a spot color", nameStr) + } + } +} + +func (f *Fpdf) getSpotColor(nameStr string) (clr spotColorType, ok bool) { + if f.err == nil { + clr, ok = f.spotColorMap[nameStr] + if !ok { + f.err = fmt.Errorf("spot color name \"%s\" is not registered", nameStr) + } + } + return +} + +// SetDrawSpotColor sets the current draw color to the spot color associated +// with nameStr. An error occurs if the name is not associated with a color. +// The value for tint ranges from 0 (no intensity) to 100 (full intensity). It +// is quietly bounded to this range. +func (f *Fpdf) SetDrawSpotColor(nameStr string, tint byte) { + var clr spotColorType + var ok bool + + clr, ok = f.getSpotColor(nameStr) + if ok { + f.color.draw.mode = colorModeSpot + f.color.draw.spotStr = nameStr + f.color.draw.str = sprintf("/CS%d CS %.3f SCN", clr.id, float64(byteBound(tint))/100) + if f.page > 0 { + f.out(f.color.draw.str) + } + } +} + +// SetFillSpotColor sets the current fill color to the spot color associated +// with nameStr. An error occurs if the name is not associated with a color. +// The value for tint ranges from 0 (no intensity) to 100 (full intensity). It +// is quietly bounded to this range. +func (f *Fpdf) SetFillSpotColor(nameStr string, tint byte) { + var clr spotColorType + var ok bool + + clr, ok = f.getSpotColor(nameStr) + if ok { + f.color.fill.mode = colorModeSpot + f.color.fill.spotStr = nameStr + f.color.fill.str = sprintf("/CS%d cs %.3f scn", clr.id, float64(byteBound(tint))/100) + f.colorFlag = f.color.fill.str != f.color.text.str + if f.page > 0 { + f.out(f.color.fill.str) + } + } +} + +// SetTextSpotColor sets the current text color to the spot color associated +// with nameStr. An error occurs if the name is not associated with a color. +// The value for tint ranges from 0 (no intensity) to 100 (full intensity). It +// is quietly bounded to this range. +func (f *Fpdf) SetTextSpotColor(nameStr string, tint byte) { + var clr spotColorType + var ok bool + + clr, ok = f.getSpotColor(nameStr) + if ok { + f.color.text.mode = colorModeSpot + f.color.text.spotStr = nameStr + f.color.text.str = sprintf("/CS%d cs %.3f scn", clr.id, float64(byteBound(tint))/100) + f.colorFlag = f.color.text.str != f.color.text.str + } +} + +func (f *Fpdf) returnSpotColor(clr colorType) (name string, c, m, y, k byte) { + var spotClr spotColorType + var ok bool + + name = clr.spotStr + if name != "" { + spotClr, ok = f.getSpotColor(name) + if ok { + c = spotClr.val.c + m = spotClr.val.m + y = spotClr.val.y + k = spotClr.val.k + } + } + return +} + +// GetDrawSpotColor returns the most recently used spot color information for +// drawing. This will not be the current drawing color if some other color type +// such as RGB is active. If no spot color has been set for drawing, zero +// values are returned. +func (f *Fpdf) GetDrawSpotColor() (name string, c, m, y, k byte) { + return f.returnSpotColor(f.color.draw) +} + +// GetTextSpotColor returns the most recently used spot color information for +// text output. This will not be the current text color if some other color +// type such as RGB is active. If no spot color has been set for text, zero +// values are returned. +func (f *Fpdf) GetTextSpotColor() (name string, c, m, y, k byte) { + return f.returnSpotColor(f.color.text) +} + +// GetFillSpotColor returns the most recently used spot color information for +// fill output. This will not be the current fill color if some other color +// type such as RGB is active. If no fill spot color has been set, zero values +// are returned. +func (f *Fpdf) GetFillSpotColor() (name string, c, m, y, k byte) { + return f.returnSpotColor(f.color.fill) +} + +func (f *Fpdf) putSpotColors() { + for k, v := range f.spotColorMap { + f.newobj() + f.outf("[/Separation /%s", strings.Replace(k, " ", "#20", -1)) + f.out("/DeviceCMYK <<") + f.out("/Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0] ") + f.outf("/C1 [%.3f %.3f %.3f %.3f] ", float64(v.val.c)/100, float64(v.val.m)/100, + float64(v.val.y)/100, float64(v.val.k)/100) + f.out("/FunctionType 2 /Domain [0 1] /N 1>>]") + f.out("endobj") + v.objID = f.n + f.spotColorMap[k] = v + } +} + +func (f *Fpdf) spotColorPutResourceDict() { + f.out("/ColorSpace <<") + for _, clr := range f.spotColorMap { + f.outf("/CS%d %d 0 R", clr.id, clr.objID) + } + f.out(">>") +} -- cgit v1.2.1-24-ge1ad