UNPKG

graphics-ts

Version:

A porting of purescript-{canvas, free-canvas, drawing} featuring fp-ts

364 lines (363 loc) 11.9 kB
"use strict"; 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) };