Drawing with FabricJS and TypeScript Part 4: Text and Freeform Lines
Tags: typescript, fabricjs, tutorial, dotnet
Published: 2020-05-07 15:20:33

Let's continue adding useful tools to [our FabricJS canvas](https://dev.to/exceptionnotfnd/drawing-with-fabricjs-and-typescript-part-1-intro-goals-and-setup-1imo) by implementing text and freeform lines!

![](https://dev-to-uploads.s3.amazonaws.com/i/7g261e59704vduhztgg2.jpg)<figcaption>Maybe letter magnets would be a good toolbar item? Photo by [Jason Leung](https://unsplash.com/@ninjason?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit)</figcaption>

##The Sample Project
As with all my code-heavy series, there is an example project over on GitHub that shows all the code we have developed and will include in this series. [Check it out](https://github.com/exceptionnotfound/FabricJSDrawingWalkthrough)!

##Text
Continuing from [the previous post](https://dev.to/exceptionnotfnd/drawing-with-fabricjs-and-typescript-part-3-basic-shapes-d64), let's create the drawer and display component classes for a text object. The below drawer uses the [FabricJS Text object](http://fabricjs.com/docs/fabric.Text.html) to represent text on the canvas.

```javascript
class TextDrawer implements IObjectDrawer {
    drawingMode: DrawingMode = DrawingMode.Text;

    make(x: number, y: number, 
         options: fabric.IObjectOptions): Promise<fabric.Object> {
         //We will need to render a textbox for the text to draw
        const text = <HTMLInputElement>document.getElementById('textComponentInput');
        return new Promise<fabric.Object>(resolve => {
            resolve(new fabric.Text(text.value, {
                left: x,
                top: y,
                ...options
            }));
        });
    }

    resize(object: fabric.Text, x: number, y: number): Promise<fabric.Object> {
        object.set({
            left: x,
            top: y
        }).setCoords();

        return new Promise<fabric.Object>(resolve => {
            resolve(object);
        });
    }
}
```

Now, let's see the component:

```javascript
class TextDisplayComponent extends DisplayComponent {

    constructor(target: string, parent: DrawingEditor) {
        const options = new DisplayComponentOptions();
        Object.assign(options, {
            altText: 'Text',
            classNames: 'fa fa-font',
            childName: 'textComponentInput'
        });
        super(DrawingMode.Text, target, parent, options);
    }

    render(): void {
        super.render();
        //We need to render a hidden textbox next to the text button.
        $(this.target).parent().append(`<input id="${this.childName}" class="col-sm-6 form-control hidden" />`);
    }

    //The two methods below, selectionUpdated and selectedChanged,
    //only exist on the base DisplayComponent class
    //because this TextDisplayComponent class needs them.
    
    //Show the textbox if the text button is selected
    selectionUpdated(newTarget: string) {
        $(newTarget).removeClass('hidden');
    }

    selectedChanged(componentName: string): void {
        //If the text button is selected, show the textbox
        if (componentName === this.target) {
            $(`#${this.childName}`).removeClass('hidden');
        }
        //Otherwise, hide the textbox.
        else {
            $(`#${this.childName}`).addClass('hidden').val('');
        }
    }
}
```

Finally, let's update the main *DrawingEditor* class, the Razor Page markup, and the script:

```javascript
class DrawingEditor {
    //...Properties

    constructor(private readonly selector: string,
        //...Rest of constructor
        this.drawers = [
            new LineDrawer(),
            new RectangleDrawer(),
            new OvalDrawer(),
            new TriangleDrawer(),
            new TextDrawer()
        ];
    }
    
    //...Other methods
    
     addComponent(target: string, component: string) {
        switch (component) {
            case 'line':
                this.components[component] 
                  = [new LineDisplayComponent(target, this)];
                break;
            case 'rect':
                this.components[component] 
                  = [new RectangleDisplayComponent(target, this)];
                break;
            case 'oval':
                this.components[component] 
                  = [new OvalDisplayComponent(target, this)];
                break;
            case 'tria':
                this.components[component] 
                  = [new TriangleDisplayComponent(target, this)];
                break;
            case 'text':
                //New component
                this.components[component] 
                  = [new TextDisplayComponent(target, this)];
                break;
        }
    }
```

```html
<div class="row bg-secondary text-light">
    <div class="col-sm-11">
        <div class="row col-12">
            <div class="row drawingToolbar">
                <label class="col-form-label controlLabel">Tools:</label>
                <div class="d-inline-block">
                    <div class="btn-group btn-group-toggle" role="group" data-toggle="buttons" aria-label="Drawing Components">
                        <div id="lineDisplayComponent"></div>
                        <div id="rectangleDisplayComponent"></div>
                        <div id="ovalDisplayComponent"></div>
                        <div id="triangleDisplayComponent"></div>
                        <!-- New Component -->
                        <div id="textDisplayComponent"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
```

```javascript
@section Scripts{
    //Initialize scripts
    <script>
        $(function () {
            //Instantiate DrawingEditor

            //Create a list of available display components
            const components = [
                { id: '#lineDisplayComponent', type: 'line' },
                { id: '#rectangleDisplayComponent', type: 'rect' },
                { id: '#ovalDisplayComponent', type: 'oval' },
                { id: '#triangleDisplayComponent', type: 'tria' },
                { id: '#textDisplayComponent', type: 'text' },
            ];

            //Add the components to the DrawingEditor, which will render them.
            editor.addComponents(components);
            $('#lineDisplayComponent').click();
        });
    </script>
}
```

With all of these changes in place, we can now write text onto our canvas, as shown in this GIF:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mve626dvyyblw5p2z0f6.gif)

Note that our implementation did not require us to change things like the text font, size, style, or color, and so I leave those kinds of improvements up to you, my dear readers.

Let's also take a few minutes to implement another useful FabricJS feature: polylines.

##Freeform Lines (AKA Polylines)

"Freeform" lines are lines which are drawn freely onto the canvas: think using the pencil tool in Paint. [FabricJS terms these "polylines"](http://fabricjs.com/docs/fabric.Polyline.html) because in reality a "freeform" line consists of many tiny straight lines that combine to form what looks like curves. These straight lines are created by storing a list of ordinal points, between which lines are connected (essentially like playing a giant version of [connect-the-dots](https://en.wikipedia.org/wiki/Connect_the_dots)). 

As with Text, we need two parts: a drawer and a display component. Here's the drawer class:

```javascript
class PolylineDrawer implements IObjectDrawer {
    drawingMode: DrawingMode = DrawingMode.Polyline;

    make(x: number, y: number, options: fabric.IObjectOptions, 
         rx?: number, ry?: number): Promise<fabric.Object> {
        return new Promise<fabric.Object>(resolve => {
            resolve(new fabric.Polyline(
                [{ x, y }],
                { ...options, fill: 'transparent' }
            ));
        });
    }

    resize(object: fabric.Polyline, x: number, y: number)
        : Promise<fabric.Object> {
        //Create and push a new Point for the Polyline
        object.points.push(new fabric.Point(x, y));
        const dim = object._calcDimensions();

        object.set({
            left: dim.left,
            top: dim.top,
            width: dim.width,
            height: dim.height,
            dirty: true,
            pathOffset: new fabric.Point(dim.left + dim.width / 2, dim.top + dim.height / 2)
        }).setCoords();

        return new Promise<fabric.Object>(resolve => {
            resolve(object);
        });
    }
}
```

And here's the display component:

```javascript
class PolylineDisplayComponent extends DisplayComponent {
    constructor(target: string, parent: DrawingEditor) {
        const options = new DisplayComponentOptions();
        Object.assign(options, {
            altText: 'Pencil',
            classNames: 'fa fa-pencil',
            childName: null
        });
        super(DrawingMode.Polyline, target, parent, options);
    }
}
```

Just as with the earlier shape drawers and components, we need to modify the *DrawingEditor* class...

```javascript
class DrawingEditor {
    //...Properties

    constructor(private readonly selector: string,
        //...Initialize canvas

        this.drawers = [
            new LineDrawer(),
            new RectangleDrawer(),
            new OvalDrawer(),
            new TriangleDrawer(),
            new TextDrawer(),
            new PolylineDrawer()
        ];
        //...Rest of constructor
    }
    
    //...Other methods

    addComponent(target: string, component: string) {
        switch (component) {
            case 'line':
                this.components[component] 
                    = [new LineDisplayComponent(target, this)];
                break;
            case 'rect':
                this.components[component] 
                    = [new RectangleDisplayComponent(target, this)];
                break;
            case 'oval':
                this.components[component] 
                    = [new OvalDisplayComponent(target, this)];
                break;
            case 'tria':
                this.components[component] 
                    = [new TriangleDisplayComponent(target, this)];
                break;
            case 'text':
                this.components[component] 
                    = [new TextDisplayComponent(target, this)];
                break;
            //New component
            case 'polyline':
                this.components[component] 
                    = [new PolylineDisplayComponent(target, this)];
                break;
        }
    }
}
```

...as well as the markup and script on our Razor Page.

```html
<div class="row bg-secondary text-light">
    <div class="col-sm-11">
        <div class="row col-12">
            <div class="row drawingToolbar">
                <label class="col-form-label controlLabel">Tools:</label>
                <div class="d-inline-block">
                    <div class="btn-group btn-group-toggle" role="group" data-toggle="buttons" aria-label="Drawing Components">
                    	<!-- New Component -->
                        <div id="polylineDisplayComponent"></div>
                        <div id="lineDisplayComponent"></div>
                        <div id="rectangleDisplayComponent"></div>
                        <div id="ovalDisplayComponent"></div>
                        <div id="triangleDisplayComponent"></div>
                        <div id="textDisplayComponent"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
```

```javascript
@section Scripts{
    //Add scripts
    <script>
        var editor;
        $(function () {
            //Instantiate DrawingEditor

            //Create a list of available display components
            const components = [
                { id: '#lineDisplayComponent', type: 'line' },
                { id: '#rectangleDisplayComponent', type: 'rect' },
                { id: '#ovalDisplayComponent', type: 'oval' },
                { id: '#triangleDisplayComponent', type: 'tria' },
                { id: '#textDisplayComponent', type: 'text' },
                //New component
                { id: '#polylineDisplayComponent', type: 'polyline' }
            ];

            //Add the components to the DrawingEditor, which will render them.
            editor.addComponents(components);
            $('#lineDisplayComponent').click();

            //...Other methods
        });
    </script>
}
```


##GIF Time!
All of this together allows us to draw freeform lines, as shown in the below GIF:

![](https://dev-to-uploads.s3.amazonaws.com/i/hp8z0ah9s6mga2y6pqsk.gif)

Ta-da! Now we have some very useful tools added to our FabricJS canvas toolbox!

##Summary

Just like with the basic shapes, for both Text and Polylines we needed to implement a drawer and a display component. Those classes then needed to be wired up to the main DrawingEditor class, and into the Razor Page markup and script.

Don't forget to check out [the sample project over on GitHub](https://github.com/exceptionnotfound/FabricJSDrawingWalkthrough)!

In the next part of this series, we will implement a toolbar button and hotkey functionality to delete objects from the canvas.

Happy Drawing!