You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
typesetting/pitfall/pdfkit/lib/gradient.coffee

179 lines
4.3 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
setTransform: (m11, m12, m21, m22, dx, dy) ->
@transform = [m11, m12, m21, m22, dx, dy]
return this
embed: (m) ->
return if @stops.length is 0
@embedded = yes
@matrix = m
# 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)
shader = @shader fn
shader.end()
pattern = @doc.ref
Type: 'Pattern'
PatternType: 2
Shading: shader
Matrix: (+v.toFixed(5) for v in @matrix)
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(@matrix)
pageBBox = [0, 0, @doc.page.width, @doc.page.height]
form = @doc.ref
Type: 'XObject'
Subtype: 'Form'
FormType: 1
BBox: pageBBox
Group:
Type: 'Group'
S: 'Transparency'
CS: 'DeviceGray'
Resources:
ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
Pattern:
Sh1: grad
form.write "/Pattern cs /Sh1 scn"
form.end "#{pageBBox.join(" ")} re f"
gstate = @doc.ref
Type: 'ExtGState'
SMask:
Type: 'Mask'
S: 'Luminosity'
G: form
gstate.end()
opacityPattern = @doc.ref
Type: 'Pattern'
PatternType: 1
PaintType: 1
TilingType: 2
BBox: pageBBox
XStep: pageBBox[2]
YStep: pageBBox[3]
Resources:
ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
Pattern:
Sh1: pattern
ExtGState:
Gs1: gstate
opacityPattern.write "/Gs1 gs /Pattern cs /Sh1 scn"
opacityPattern.end "#{pageBBox.join(" ")} re f"
@doc.page.patterns[@id] = opacityPattern
else
@doc.page.patterns[@id] = pattern
return pattern
apply: (op) ->
# apply gradient transform to existing document ctm
[m0, m1, m2, m3, m4, m5] = @doc._ctm.slice()
[m11, m12, m21, m22, dx, dy] = @transform
m = [m0 * m11 + m2 * m12,
m1 * m11 + m3 * m12,
m0 * m21 + m2 * m22,
m1 * m21 + m3 * m22,
m0 * dx + m2 * dy + m4,
m1 * dx + m3 * dy + m5]
@embed(m) unless @embedded and m.join(" ") is @matrix.join(" ")
@doc.addContent "/#{@id} #{op}"
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}