mirror of
https://github.com/foliojs/pdfkit.git
synced 2025-12-08 20:15:54 +00:00
169 lines
4.0 KiB
CoffeeScript
169 lines
4.0 KiB
CoffeeScript
class PDFGradient
|
|
constructor: (@doc) ->
|
|
@stops = []
|
|
@embedded = no
|
|
@transform = [1, 0, 0, 1, 0, 0]
|
|
@_colorSpace = 'DeviceRGB'
|
|
|
|
stop: (pos, color, opacity = 1) ->
|
|
opacity = Math.max(0, Math.min(1, opacity))
|
|
@stops.push [pos, @doc._normalizeColor(color), opacity]
|
|
return this
|
|
|
|
embed: ->
|
|
return if @embedded or @stops.length is 0
|
|
@embedded = yes
|
|
|
|
# if the last stop comes before 100%, add a copy at 100%
|
|
last = @stops[@stops.length - 1]
|
|
if last[0] < 1
|
|
@stops.push [1, last[1], last[2]]
|
|
|
|
bounds = []
|
|
encode = []
|
|
stops = []
|
|
|
|
for i in [0...@stops.length - 1]
|
|
encode.push 0, 1
|
|
unless i + 2 is @stops.length
|
|
bounds.push @stops[i + 1][0]
|
|
|
|
fn = @doc.ref
|
|
FunctionType: 2
|
|
Domain: [0, 1]
|
|
C0: @stops[i + 0][1]
|
|
C1: @stops[i + 1][1]
|
|
N: 1
|
|
|
|
stops.push fn
|
|
fn.end()
|
|
|
|
# if there are only two stops, we don't need a stitching function
|
|
if stops.length is 1
|
|
fn = stops[0]
|
|
else
|
|
fn = @doc.ref
|
|
FunctionType: 3 # stitching function
|
|
Domain: [0, 1]
|
|
Functions: stops
|
|
Bounds: bounds
|
|
Encode: encode
|
|
|
|
fn.end()
|
|
|
|
@id = 'Sh' + (++@doc._gradCount)
|
|
|
|
# apply gradient transform to existing document ctm
|
|
m = @doc._ctm.slice()
|
|
[m0, m1, m2, m3, m4, m5] = m
|
|
[m11, m12, m21, m22, dx, dy] = @transform
|
|
m[0] = m0 * m11 + m2 * m12
|
|
m[1] = m1 * m11 + m3 * m12
|
|
m[2] = m0 * m21 + m2 * m22
|
|
m[3] = m1 * m21 + m3 * m22
|
|
m[4] = m0 * dx + m2 * dy + m4
|
|
m[5] = m1 * dx + m3 * dy + m5
|
|
|
|
shader = @shader fn
|
|
shader.end()
|
|
|
|
pattern = @doc.ref
|
|
Type: 'Pattern'
|
|
PatternType: 2
|
|
Shading: shader
|
|
Matrix: (+v.toFixed(5) for v in m)
|
|
|
|
@doc.page.patterns[@id] = pattern
|
|
pattern.end()
|
|
|
|
if (@stops.some (stop) -> stop[2] < 1)
|
|
grad = @opacityGradient()
|
|
grad._colorSpace = 'DeviceGray'
|
|
|
|
for stop in @stops
|
|
grad.stop stop[0], [stop[2]]
|
|
|
|
grad = grad.embed()
|
|
|
|
group = @doc.ref
|
|
Type: 'Group'
|
|
S: 'Transparency'
|
|
CS: 'DeviceGray'
|
|
|
|
group.end()
|
|
|
|
resources = @doc.ref
|
|
ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
|
|
Shading:
|
|
Sh1: grad.data.Shading
|
|
|
|
resources.end()
|
|
|
|
form = @doc.ref
|
|
Type: 'XObject'
|
|
Subtype: 'Form'
|
|
FormType: 1
|
|
BBox: [0, 0, @doc.page.width, @doc.page.height]
|
|
Group: group
|
|
Resources: resources
|
|
|
|
form.end "/Sh1 sh"
|
|
|
|
sMask = @doc.ref
|
|
Type: 'Mask'
|
|
S: 'Luminosity'
|
|
G: form
|
|
|
|
sMask.end()
|
|
|
|
gstate = @doc.ref
|
|
Type: 'ExtGState'
|
|
SMask: sMask
|
|
|
|
@opacity_id = ++@doc._opacityCount
|
|
name = "Gs#{@opacity_id}"
|
|
|
|
@doc.page.ext_gstates[name] = gstate
|
|
gstate.end()
|
|
|
|
return pattern
|
|
|
|
apply: (op) ->
|
|
@embed() unless @embedded
|
|
@doc.addContent "/#{@id} #{op}"
|
|
|
|
if @opacity_id
|
|
@doc.addContent "/Gs#{@opacity_id} gs"
|
|
@doc._sMasked = true
|
|
|
|
class PDFLinearGradient extends PDFGradient
|
|
constructor: (@doc, @x1, @y1, @x2, @y2) ->
|
|
super
|
|
|
|
shader: (fn) ->
|
|
@doc.ref
|
|
ShadingType: 2
|
|
ColorSpace: @_colorSpace
|
|
Coords: [@x1, @y1, @x2, @y2]
|
|
Function: fn
|
|
Extend: [true, true]
|
|
|
|
opacityGradient: ->
|
|
return new PDFLinearGradient(@doc, @x1, @y1, @x2, @y2)
|
|
|
|
class PDFRadialGradient extends PDFGradient
|
|
constructor: (@doc, @x1, @y1, @r1, @x2, @y2, @r2) ->
|
|
super
|
|
|
|
shader: (fn) ->
|
|
@doc.ref
|
|
ShadingType: 3
|
|
ColorSpace: @_colorSpace
|
|
Coords: [@x1, @y1, @r1, @x2, @y2, @r2]
|
|
Function: fn
|
|
Extend: [true, true]
|
|
|
|
opacityGradient: ->
|
|
return new PDFRadialGradient(@doc, @x1, @y1, @r1, @x2, @y2, @r2)
|
|
|
|
module.exports = {PDFGradient, PDFLinearGradient, PDFRadialGradient} |