Rich text

Out of the box, Polotno does not support the rendering of rich text elements, meaning you cannot apply different styles, colors, fonts, etc., to parts of the text. The entire text element must maintain a uniform style. However, Polotno offers support for rich text elements, which must be explicitly activated.

How to enable rich text support?

import { unstable_useHtmlTextRender } from 'polotno/config';

unstable_useHtmlTextRender(true);

// later in store
store.activePage.addElement({
  type: 'text',
  text: 'Hello <strong>from rich</strong> <u>text</u> <span style="color: red;">support</span>!',
  y: 300,
  x: store.width / 2 - 200,
  fontSize: 80,
  width: 400,
});

When 'html renderer' is enabled, Polotno will use very different rendering logic for text elements. Rich text may drop the performance of your application if you have many text elements. Please report any issues you may come across.

Polotno has many clients who use these settings on production. The feature considered as stable and supported. The "unstable" prefix will be removed soon.

If you use Cloud Render API, please make sure to pass htmlTextRenderEnabled: true to enable rich mode.

Controlling rich text from external UI

Internally, rich text is represented as HTML string. You can modify it with any tools to apply the changes. There are some built-in methods that you can use to change formats.

Internally, Polotno is using https://quilljs.com/ library to manipulate text. When text element is in "edit mode" (when user double-click on it) you can use these methods:

import { quillRef } from 'polotno/canvas/html-element';

// will log observable object with format of current selection
console.log(quillRef.currentFormat); 

// will log current Quill editor instance. if text is not in edit mode, it will return null
console.log(quillRef.editor.intance);

<Button
  // (!) IMPORTANT to prevent default behaviour
  // so the quill editor doesn't lose the focus!
  onMouseDown={(e) => {
    e.preventDefault();
  }}
  onClick={() => {
    // if you want to change format to bold of current selection:
    const quill = quillRef.editor.intance;
    const selection = quill.getSelection();
    // toggle bold for selection
    quill.formatText(
      selection.index,
      selection.length,
      'bold',
      !quillRef.currentFormat.bold,
      'user'
    );
  }}
/>

In numerous instances, you may want to change format, while a text element is not in edit mode. In that case you can create a Quill instance temporary in memory -> apply changes -> save resulted HTML into the element.

import { createQuill, setQuillContent } from 'polotno/canvas/html-element';

// function to create quill instance in memory
const createTempQuill = ({ html }) => {
  const el = document.createElement('div');
  document.body.appendChild(el);
  // el.innerHTML = html;
  el.style.display = 'none';
  el.style.whiteSpace = 'pre-wrap';
  const quill = createQuill(el);
  // use setQuillContent function to preserve some history edge cases
  setQuillContent(quill, html);
  return quill;
};

// remove quill when finished
const removeTempQuill = (quill) => {
  quill.root.parentElement.remove();
};

// example usage
const quill = createTempQuill({ html: element.text });
quill.setSelection(0, quill.getLength(), 'api');
// remove bold from text
quill.format('bold', false);
const innerHtml = quill.root.innerHTML;
// remove quill from memory
removeTempQuill(quill);
// save HTML back to element
element.set({ text: innerHtml });

Here is a full real-world example of "bold" format button:

const ToggleButton = observer(
  ({
    active,
    globalActive,
    // name of the format
    format,
    element,
    disableGlobal,
    enableGlobal,
    icon,
  }: {
    active: boolean;
    globalActive: boolean;
    format: string;
    element: TextElementType;
    disableGlobal: () => void;
    enableGlobal: () => void;
    icon: any;
  }) => {
    return (
      <Button
        minimal
        icon={icon}
        active={active}
        onMouseDown={(e) => {
          e.preventDefault();
        }}
        onClick={(e) => {
          let quill = window.__polotnoQuill;

          if (quill) {
            const selection = quill.getSelection();
            quill.formatText(
              selection.index,
              selection.length,
              format,
              !quillRef.currentFormat[format],
              'user'
            );
            if (globalActive) {
              disableGlobal();
            }
            return;
          }

          // if whole text selected, let's remove bold from inner
          quill = createTempQuill({ html: element.text });
          quill.setSelection(0, quill.getLength(), 'api');
          quill.format(format, false);
          const innerHtml = quill.root.innerHTML;
          removeTempQuill(quill);
          element.set({ text: innerHtml });

          if (globalActive) {
            disableGlobal();
          } else {
            enableGlobal();
          }
        }}
      />
    );
  }
);

export const TextBold = observer(({ element, store }) => {
  return (
    <ToggleButton
      format="bold"
      active={
        quillRef.currentFormat.bold ||
        element.fontWeight === 'bold' ||
        element.fontWeight === '700'
      }
      globalActive={
        element.fontWeight === 'bold' || element.fontWeight === '700'
      }
      element={element}
      disableGlobal={() => element.set({ fontWeight: 'normal' })}
      enableGlobal={() => element.set({ fontWeight: 'bold' })}
      icon={<Bold />}
    />
  );
});

Define quill editor formats

You can hook into the quill editor to change the list of available formats or define your own:

import { unstable_setQuillFormats } from 'polotno/config';

unstable_setQuillFormats([
  // default list of formats
  'bold',
  'color',
  'font',
  'italic',
  'size',
  'strike',
  'underline',
  'indent',
  'list',
  'direction',
  // add additional format
  'font-weight'
]);

// implement a new format with quill API
import Quill from "quill";
const Inline = Quill.import("blots/inline");

class FontWeightBlot extends Inline {
  static create(value) {
    const node = super.create();
    node.style.fontWeight = value;
    return node;
  }

  static formats(node) {
    return node.style.fontWeight;
  }
}

FontWeightBlot.blotName = "font-weight";
FontWeightBlot.tagName = "span";

Quill.register(FontWeightBlot);


News, updates and promos – be the first to get 'em

News, updates and promos – be the first to get 'em