graphics-ts
Version:
A porting of purescript-{canvas, free-canvas, drawing} featuring fp-ts
364 lines (363 loc) • 11.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.monoidDrawing = exports.monoidShadow = exports.monoidOutlineStyle = exports.monoidFillStyle = exports.render = exports.renderShape = exports.shadowOffset = exports.shadowColor = exports.shadowBlur = exports.withShadow = exports.translate = exports.text = exports.scale = exports.rotate = exports.many = exports.lineWidth = exports.outlineColor = exports.outline = exports.fillStyle = exports.fill = exports.clipped = void 0;
/**
* The `Drawing` module abstracts away the repetitive calls to the HTML Canvas API that are required
* when using the `Canvas` module directly and instead allows us to be more declarative with our code.
*
* Taking the MDN example from the `Canvas` documentation,
*
* ```ts
* import { error } from 'fp-ts/lib/Console'
* import { pipe } from 'fp-ts/lib/pipeable'
* import * as R from 'fp-ts-contrib/lib/ReaderIO'
* import * as C from 'graphics-ts/lib/Canvas'
* import * as Color from 'graphics-ts/lib/Color'
* import * as S from 'graphics-ts/lib/Shape'
*
* const canvasId = 'canvas'
*
* const triangle: C.Render<void> = C.fillPath(
* pipe(
* C.setFillStyle(pipe(Color.black, Color.toCss)),
* R.chain(() => C.moveTo(S.point(75, 50))),
* R.chain(() => C.lineTo(S.point(100, 75))),
* R.chain(() => C.lineTo(S.point(100, 25)))
* )
* )
*
* C.renderTo(canvasId, () => error(`[ERROR]: Unable to find canvas with id ${canvasId}`))(triangle)()
* ```
*
* the `triangle` renderer above becomes the following
*
* ```ts
* import { error } from 'fp-ts/lib/Console'
* import * as RA from 'fp-ts/lib/ReadonlyArray'
* import * as C from 'graphics-ts/lib/Canvas'
* import * as Color from 'graphics-ts/lib/Color'
* import * as D from 'graphics-ts/lib/Drawing'
* import * as S from 'graphics-ts/lib/Shape'
*
* const canvasId = 'canvas'
*
* const triangle: C.Render<void> = D.render(
* D.fill(
* S.path(RA.readonlyArray)([S.point(75, 50), S.point(100, 75), S.point(100, 25)]),
* D.fillStyle(Color.black)
* )
* )
*
* C.renderTo(canvasId, () => error(`[ERROR]: Unable to find canvas with id ${canvasId}`))(triangle)()
* ```
*
* Adapted from https://github.com/purescript-contrib/purescript-drawing
*
* @since 1.0.0
*/
var IO = require("fp-ts/lib/IO");
var M = require("fp-ts/lib/Monoid");
var O = require("fp-ts/lib/Option");
var RA = require("fp-ts/lib/ReadonlyArray");
var function_1 = require("fp-ts/lib/function");
var pipeable_1 = require("fp-ts/lib/pipeable");
var R = require("fp-ts-contrib/lib/ReaderIO");
var C = require("./Canvas");
var Color_1 = require("./Color");
var Font_1 = require("./Font");
var readonlyArrayMonoidDrawing = RA.getMonoid();
var getFirstMonoidColor = O.getFirstMonoid();
var getFirstMonoidNumber = O.getFirstMonoid();
var getFirstMonoidPoint = O.getFirstMonoid();
var traverseReaderIO = RA.readonlyArray.traverse(R.readerIO);
// -------------------------------------------------------------------------------------
// constructors
// -------------------------------------------------------------------------------------
/**
* Clips a `Drawing` using the specified `Shape`.
*
* @category constructors
* @since 1.0.0
*/
exports.clipped = function (shape, drawing) { return ({
_tag: 'Clipped',
shape: shape,
drawing: drawing
}); };
/**
* Constructs a `Drawing` from a `Fill` `Shape`.
*
* @category constructors
* @since 1.0.0
*/
exports.fill = function (shape, style) { return ({ _tag: 'Fill', shape: shape, style: style }); };
/**
* Constructs a `FillStyle`.
*
* @category constructors
* @since 1.0.0
*/
exports.fillStyle = function (c) { return ({ color: O.some(c) }); };
/**
* Constructs a `Drawing` from an `Outline` `Shape`.
*
* @category constructors
* @since 1.0.0
*/
exports.outline = function (shape, style) { return ({
_tag: 'Outline',
shape: shape,
style: style
}); };
/**
* Constructs an `OutlineStyle` from a `Color`.
*
* @category constructors
* @since 1.0.0
*/
exports.outlineColor = function (c) { return ({
color: O.some(c),
lineWidth: O.none
}); };
/**
* Constructs an `OutlineStyle` from a line width.
*
* @category constructors
* @since 1.0.0
*/
exports.lineWidth = function (w) { return ({
color: O.none,
lineWidth: O.some(w)
}); };
/**
* Construct a single `Drawing` from a collection of `Many` `Drawing`s.
*
* @category constructors
* @since 1.0.0
*/
exports.many = function (drawings) { return ({ _tag: 'Many', drawings: drawings }); };
/**
* Applies rotation to the transform of a `Drawing`.
*
* @category constructors
* @since 1.0.0
*/
exports.rotate = function (angle, drawing) { return ({
_tag: 'Rotate',
angle: angle,
drawing: drawing
}); };
/**
* Applies scale to the transform of a `Drawing`.
*
* @category constructors
* @since 1.0.0
*/
exports.scale = function (scaleX, scaleY, drawing) { return ({
_tag: 'Scale',
scaleX: scaleX,
scaleY: scaleY,
drawing: drawing
}); };
/**
* Constructs a `Drawing` from `Text`.
*
* @category constructors
* @since 1.0.0
*/
exports.text = function (font, x, y, style, text) { return ({
_tag: 'Text',
font: font,
x: x,
y: y,
style: style,
text: text
}); };
/**
* Applies translation to the transform of a `Drawing`.
*
* @category constructors
* @since 1.0.0
*/
exports.translate = function (translateX, translateY, drawing) { return ({
_tag: 'Translate',
translateX: translateX,
translateY: translateY,
drawing: drawing
}); };
/**
* Applies `Shadow` to a `Drawing`.
*
* @category constructors
* @since 1.0.0
*/
exports.withShadow = function (shadow, drawing) { return ({
_tag: 'WithShadow',
shadow: shadow,
drawing: drawing
}); };
/**
* Constructs a `Shadow` from a blur radius.
*
* @category constructors
* @since 1.0.0
*/
exports.shadowBlur = function (b) { return ({
color: O.none,
blur: O.some(b),
offset: O.none
}); };
/**
* Constructs a `Shadow` from a `Color`.
*
* @category constructors
* @since 1.0.0
*/
exports.shadowColor = function (c) { return ({
color: O.some(c),
blur: O.none,
offset: O.none
}); };
/**
* Constructs a `Shadow` from an offset `Point`.
*
* @category constructors
* @since 1.0.0
*/
exports.shadowOffset = function (o) { return ({
color: O.none,
blur: O.none,
offset: O.some(o)
}); };
// -------------------------------------------------------------------------------------
// combinators
// -------------------------------------------------------------------------------------
var applyStyle = function (fa, f) {
return pipeable_1.pipe(fa, O.fold(function () { return IO.of; }, f));
};
/**
* Renders a `Shape`.
*
* @category combinators
* @since 1.1.0
*/
exports.renderShape = function (shape) {
switch (shape._tag) {
case 'Arc':
return C.arc(shape);
case 'Composite':
return pipeable_1.pipe(traverseReaderIO(shape.shapes, exports.renderShape), R.chain(function () { return R.ask(); }));
case 'Ellipse':
return C.ellipse(shape);
case 'Path':
return pipeable_1.pipe(shape.points, RA.foldLeft(function () { return IO.of; }, function (head, tail) {
return pipeable_1.pipe(C.moveTo(head), R.chain(function () { return traverseReaderIO(tail, C.lineTo); }), R.chain(function () { return (shape.closed ? C.closePath : IO.of); }));
}));
case 'Rect':
return C.rect(shape);
}
};
/**
* Renders a `Drawing`.
*
* @category combinators
* @since 1.0.0
*/
exports.render = function (drawing) {
var go = function (d) {
switch (d._tag) {
case 'Clipped':
return C.withContext(pipeable_1.pipe(C.beginPath, R.chain(function () { return exports.renderShape(d.shape); }), R.chain(function () { return C.clip(); }), R.chain(function () { return go(d.drawing); })));
case 'Fill':
return C.withContext(pipeable_1.pipe(applyStyle(d.style.color, function_1.flow(Color_1.toCss, C.setFillStyle)), R.chain(function () { return C.fillPath(exports.renderShape(d.shape)); })));
case 'Many':
return pipeable_1.pipe(traverseReaderIO(d.drawings, go), R.chain(function () { return R.ask(); }));
case 'Outline':
return C.withContext(pipeable_1.pipe(applyStyle(d.style.color, function_1.flow(Color_1.toCss, C.setStrokeStyle)), R.chain(function () { return applyStyle(d.style.lineWidth, C.setLineWidth); }), R.chain(function () { return C.strokePath(exports.renderShape(d.shape)); })));
case 'Rotate':
return C.withContext(pipeable_1.pipe(C.rotate(d.angle), R.chain(function () { return go(d.drawing); })));
case 'Scale':
return C.withContext(pipeable_1.pipe(C.scale(d.scaleX, d.scaleY), R.chain(function () { return go(d.drawing); })));
case 'Text':
return C.withContext(pipeable_1.pipe(C.setFont(Font_1.showFont.show(d.font)), R.chain(function () { return applyStyle(d.style.color, function_1.flow(Color_1.toCss, C.setFillStyle)); }), R.chain(function () { return C.fillText(d.text, d.x, d.y); })));
case 'Translate':
return C.withContext(pipeable_1.pipe(C.translate(d.translateX, d.translateY), R.chain(function () { return go(d.drawing); })));
case 'WithShadow':
return C.withContext(pipeable_1.pipe(applyStyle(d.shadow.color, function_1.flow(Color_1.toCss, C.setShadowColor)), R.chain(function () { return applyStyle(d.shadow.blur, C.setShadowBlur); }), R.chain(function () {
return applyStyle(d.shadow.offset, function (o) {
return pipeable_1.pipe(C.setShadowOffsetX(o.x), R.chain(function () { return C.setShadowOffsetY(o.y); }));
});
}), R.chain(function () { return go(d.drawing); })));
}
};
return go(drawing);
};
// -------------------------------------------------------------------------------------
// instances
// -------------------------------------------------------------------------------------
/**
* Gets a `Monoid` instance for `FillStyle`.
*
* @category instances
* @since 1.0.0
*/
exports.monoidFillStyle = M.getStructMonoid({
color: getFirstMonoidColor
});
/**
* Gets a `Monoid` instance for `OutlineStyle`.
*
* @example
* import * as O from 'fp-ts/lib/Option'
* import * as M from 'fp-ts/lib/Monoid'
* import * as Color from 'graphics-ts/lib/Color'
* import * as D from 'graphics-ts/lib/Drawing'
*
* assert.deepStrictEqual(
* M.fold(D.monoidOutlineStyle)([
* D.outlineColor(Color.black),
* D.outlineColor(Color.white),
* D.lineWidth(5)
* ]),
* {
* color: O.some(Color.black),
* lineWidth: O.some(5)
* }
* )
*
* @category instances
* @since 1.0.0
*/
exports.monoidOutlineStyle = M.getStructMonoid({
color: getFirstMonoidColor,
lineWidth: getFirstMonoidNumber
});
/**
* Gets a `Monoid` instance for `Shadow`.
*
* @category instances
* @since 1.0.0
*/
exports.monoidShadow = M.getStructMonoid({
color: getFirstMonoidColor,
blur: getFirstMonoidNumber,
offset: getFirstMonoidPoint
});
/**
* Gets a `Monoid` instance for `Drawing`.
*
* @category instances
* @since 1.0.0
*/
exports.monoidDrawing = {
concat: function (x, y) {
return x._tag === 'Many' && y._tag === 'Many'
? exports.many(M.fold(readonlyArrayMonoidDrawing)([x.drawings, y.drawings]))
: x._tag === 'Many'
? exports.many(M.fold(readonlyArrayMonoidDrawing)([x.drawings, [y]]))
: y._tag === 'Many'
? exports.many(M.fold(readonlyArrayMonoidDrawing)([[x], y.drawings]))
: exports.many([x, y]);
},
empty: exports.many(readonlyArrayMonoidDrawing.empty)
};