Kinetiqa Blog Homepage
Bleilettern in einem Setzkasten in Zeilenform. Die Buchstaben sind spiegelverkehrt

Einfacher Zeilenumbruch in Canvas

Wir hatten neulich das Problem, für einen Kunden eine JavaScript-Lösung umzusetzen, die dynamisch Text über bestehende Bilder schreibt. Nach ein wenig Recherche blieben wir bei canvas hängen, einer JavaScript-Implementierung der DOM-Canvas-Schnittstelle.

Das Problem

Schnell ist ein kleines Arbeitsbeispiel damit auf Basis seiner README-Datei zusammengestellt:

import { createCanvas, loadImage, registerFont } from 'canvas';

const text = 'Unser Beispiel-Text, der aber durchaus mal ein bisschen länger werden kann.';

registerFont('./inter.ttf', { family: 'Inter' });

const canvas = createCanvas(1200, 630);
const ctx = canvas.getContext('2d');

const base_image = await loadImage('base.png');
ctx.drawImage(base_image, 0, 0, 1200, 630);

ctx.fillStyle = 'white';
ctx.font = '64px Inter';
ctx.fillText(text, 30, 30);

Doch ebenso schnell stellt sich hier die Limitierung heraus: Wie in anderen Grafikbibliotheken wie ImageMagick oder textbasierten Bildformaten wie SVG ist fillText() eine Low-Level-Methode. Um Dinge wie Silbentrennung, Fettdruck, Einrückungen usw. muss man sich selbst kümmern.

Ein einfacher Zeilenumbruch-Algorithmus

Wir brauchten deshalb eine Lösung für einen Zeilenumbruch, der dafür sorgt, dass langer Text nicht am rechten Rand das Bild verlässt.

Die Canvas-Schnittstelle bietet hier eine für unsere Zwecke wunderbare Methode, die Länge eines Wortes zu messen, bevor wir es ausgeben, .measureText(). Dadurch lässt sich mit wenigen Zeilen ein rudimentärer Zeilenumbruch umsetzen:

// ein paar Werte für später:
const zeilenHöhe = 84;
const maxZeilenLänge = 1140;

// wir teilen den Text in Wörter:
const wörter = text.split(/ +/g);

let yOffset = 30;

while (wörter.length) {
  // bevölkere die aktuelle Zeile mit dem ersten Wort
  let zeile = wörter.shift();
  // wir messen, wie lange die Zeile wäre, wenn sie das nächste Wort zusätzlich
  // enthalten würde. Solange es reicht, fügen wir dieses der Zeile hinzu.
  while (wörter.length &&
         ctx.measureText(zeile + ' ' + wörter[0]).width < maxZeilenLänge) {
    zeile += ' ' + wörter.shift();
  }
  // jetzt schreiben wir die Zeile ins Bild...
  ctx.fillText(zeile, 30, yOffset);
  // ...und erhöhen den Abstand von oben für die nächste Zeile
  yOffset += zeilenHöhe;
}

Vorsicht vor der Komplexität!

Der gezeigte Algorithmus ist sehr primitiv. Er setzt einige Annahmen voraus, die im allgemeinen Fall nicht gegeben sind:

Darüber hinaus darf kein Wort vorkommen, das von sich aus bereits länger als die Zeile ist. Ansonsten wird es trotzdem auf der rechten Seite aus dem Bild ragen. Wenn Sie also den Liedtext von „Supercalifragilisticexpialigetisch“ oder eine Abhandlung über den „Donaudampfschifffahrtskapitän“ als potentiellen Eingabetext erwarten, sollten Sie eine professionellere Text-Engine verwenden.

Foto von Fabio Santaniello Bruun auf Unsplash

Nach oben Zur Startseite