Form III: Text und Typografie

Fonts, Text ausrichten, Text vermessen, Texteingabe: Keyboard, Texteingabe: Dateien, Text als Daten, Fonts als Daten: Schriften manipulieren, Interaktive Schrift, Generative Schriftkontur

Fonts

Eine Variable für Schriften: PFont. Entweder Systemschriften verwenden... (über ihren Namen, Namen anzeigen lassen mit PFont.list())

println(PFont.list());

...oder Schriftart im Data-Ordner ablegen (genau wie Bilder) und laden:

PFont f;
String s;

void setup() {
  size(400,400);
  background(0);
  s = "Abcdefg.";
  f = createFont("HypatiaSansPro-Regular.otf", 32);
}

Schrift auswählen mit textFont(Schriftvariable, Größe), anzeigen mit text().

void draw() {
  background(0);
  fill(255);
  textFont(f, 128);
  text(s, 0, height/2);
}

Alle Fonts auf dem Rechner einmal laden und benutzen (abspeichern in PDF):

import processing.pdf.*;

String text = "Form & Code";
String[] alleFonts = PFont.list();
println("Anzahl der Schriften: "+alleFonts.length);

size(600,14000,PDF,"fonts.pdf");  // Höhe der Zeichenfläche sollte Anzahl der Schriften mal 30 sein
background(255);
fill(0);
textAlign(LEFT,TOP);
translate(20,20);
for (int i=0; i<alleFonts.length; i++) {
  PFont f = createFont(alleFonts[i],24);
  textFont(f,24);
  text(alleFonts[i]+": "+text,0,0);
  translate(0,30);
  println("Schrift "+i+": "+alleFonts[i]);
}
println("...fertig.");

Text ausrichten

Die Koordinaten des Textes beziehen sich auf dessen linke untere Ecke (inkl. Ober-, Unterlängen usw.):

void draw() {
  background(0);
  fill(255);
  textFont(f, 128);
  text(s, 0, height/2);
  stroke(128,128,255);
  line(0,height/2,width,height/2);
}

textAlign(...) regelt die Ausrichtung rechts/links/zentriert...

void draw() {
  background(0);
  fill(255);
  textFont(f, 64);
  textAlign(RIGHT);
  text(s, width/2, 100);
  textAlign(CENTER);
  text(s, width/2, 200);
  textAlign(LEFT);
  text(s, width/2, 300);

  if (mousePressed) {
    stroke(128, 128, 255);
    line(0, 100, width, 100);
    line(0, 200, width, 200);
    line(0, 300, width, 300);
    line(width/2, 0, width/2, height);
  }
}

...und auch oben/mitte/unten und auf der Grundlinie (NORMAL):

void draw() {
  background(0);
  fill(255);
  textFont(f, 64);
  textAlign(RIGHT,BOTTOM);
  text(s, width/2, 100);
  textAlign(RIGHT,NORMAL);
  text(s, width/2, 200);
  textAlign(CENTER,CENTER);
  text(s, width/2, 300);
  textAlign(LEFT,TOP);
  text(s, width/2, 400);

  if (mousePressed) {
    stroke(128, 128, 255);
    line(0, 100, width, 100);
    line(0, 200, width, 200);
    line(0, 300, width, 300);
    line(0, 400, width, 400);
    line(width/2, 0, width/2, height);
  }
}

Zeilenabstand festlegen mit textLeading(...). Zeilen werden in einer String-Variablen mit \n (Newline) markiert.

size(600, 300);
background(0);
PFont f = createFont("HypatiaSansPro-Regular.otf", 44);
textFont(f, 44);
String dreiZeilen = "Aber,\naber,\naber!";

textAlign(LEFT,TOP);
textLeading(44);
text(dreiZeilen, 20, 40);
textLeading(88);
text(dreiZeilen, 220, 40);
textLeading(4);
text(dreiZeilen, 420, 40);

Text vermessen: Grundlinie, Ober-/Unterlänge, Breite

Ober- und Unterlänge liefern textAscent() und textDescent(). Die Grundlinie hängt von textAlign(...) ab.

size(400, 200);
background(0);
PFont f = createFont("HypatiaSansPro-Regular.otf", 44);
textFont(f, 44);
String zeile = "Kragen!";

textAlign(LEFT,BOTTOM);
text(zeile, 40, height/2);

float oberlaenge = textAscent();
float unterlaenge = textDescent();
float grundlinie = height/2 - unterlaenge;

stroke(128, 128, 255);
line(0,grundlinie,width,grundlinie);
stroke(255,128,128);
line(0,grundlinie-oberlaenge,width,grundlinie-oberlaenge);
line(0,grundlinie+unterlaenge,width,grundlinie+unterlaenge);

Die Breite eines Textes (bzw. eines Strings) berechnet textWidth(...).

size(400, 200);
background(0);
PFont f = createFont("HypatiaSansPro-Regular.otf", 44);
textFont(f, 44);
String zeile = "Kragen!";

textAlign(LEFT,BOTTOM);
text(zeile, 40, height/2);

float links = 40;
float rechts = 40 + textWidth(zeile);

stroke(255,128,128);
line(links,0,links,height);
line(rechts,0,rechts,height);

Zum Beispiel: Nach jedem Wort die Zeichenfläche um die Breite des Wortes verschieben.

size(600, 600);
background(0);
PFont f = createFont("HypatiaSansPro-Regular.otf", 36);
textFont(f, 36);
String[] texte = { 
  "Eins", "Zwei", "Drei", "Vier", "Fünf"
};
textAlign(LEFT, CENTER);

translate(20, 100);

for (int i=0; i<10; i++) {
  pushMatrix();
  for (int j=0; j<10; j++) {
    int zufallsIndex = int(random(5));
    String wort = texte[zufallsIndex];
    text(wort, 0, 0);
    translate(textWidth(wort), 0);
    rotate(radians(random(-10, 10)));
  }
  popMatrix();
  translate(0,36);
}

Texteingabe: Keyboard

Zugriff auf die Tastatur über eine Methode (wie setup oder draw): keyPressed().

void setup() {
  size(400,400);
  background(0);
  stroke(255,160);
}

void draw() {
}

void keyPressed() {
  line(random(width),random(height),random(width),random(height));
}

Zugriff auf die gedrückte Taste über die Variable key:

void setup() {
  size(400,400);
  background(0);
}

void draw() {
}

void keyPressed() {
  textAlign(CENTER,CENTER);
  textSize(random(10,80));
  colorMode(HSB);
  fill(random(255),200,200);
  text(key,random(width),random(height));
}

Buchstaben sind Variablen der Sorte char (Character) und im Grunde das selbe wie Zahlen. Den Zusammenhang zwischen Zahlen und Buchstaben regelt ASCII: ASCII
Ein "A" in ASCII ist die Zahl 65, ein "a" ist 97, eine "7" ist 55:

void setup() {
  size(400,400);
  background(0);
}

void draw() {
}

void keyPressed() {
  textAlign(CENTER,CENTER);
  textSize(random(10,80));
  colorMode(HSB);
  fill(random(255),200,200);
  text(key+" ("+int(key)+")",random(width),random(height));
}

Spezialtasten filtern über ihren keyCode:

void setup() {
  size(400, 400);
  background(0);
}

void draw() {
}

void keyPressed() {
  textAlign(CENTER, CENTER);
  textSize(random(10, 80));
  colorMode(HSB);
  fill(random(255), 200, 200);
  if (key==CODED) {
    if (keyCode==RIGHT) {
      background(0);
    }
  } else {
    text(key, random(width), random(height));
  }
}

Texteingabe: Dateien

Texte aus Dateien lesen mit loadStrings(...). Ergebnis ist ein Array, das die einzelnen Zeilen der Datei enthält:

String[] texte = loadStrings("textdatei.txt");

println("Zeilen in der Datei: "+texte.length);

for (int i=0; i<texte.length; i++) {
  println("Zeile "+(i+1)+": "+texte[i]);
}

...oder:

String[] texte;

void setup() {
  size(600, 600);
  texte = loadStrings("textdatei.txt");
  PFont f = createFont("HypatiaSansPro-Regular.otf", 36);
  textFont(f, 36);
}

void draw() {
  background(0);
  translate(width/2, height/2);
  rotate(radians(map(mouseX,0,width,0,360)));
  for (int i=0; i<texte.length; i++) {
    rotate(radians(36));
    text(texte[i], 40, 0);
  }
}

Text als Daten

Visualisieren von Text zum Beispiel durch Zugriff auf dessen Eigenschaften oder seine Zeichen. String.length() für die Länge eines Strings.

String[] texte;

size(600, 600);
texte = loadStrings("textdatei.txt");
noFill();

background(255);
translate(0,30);
for (int i=0; i<texte.length; i++) {
  stroke(0);
  strokeWeight(50);
  strokeCap(SQUARE);
  line(0,0,texte[i].length()*3,0);
  translate(0,60);
}

Zugriff auf die Zeichen in einem String mit String.charAt(), verschieben mit translate(...) um die Breite des Buchstaben (textWidth())...

size(800,200);
String text = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.";

background(255);
translate(0,height/2);

for (int i=0; i<text.length(); i++) {
  textAlign(LEFT, CENTER);
  textSize(random(10, 80));
  fill(random(255));
  text(text.charAt(i),0,0);
  translate(textWidth(text.charAt(i)),0);
}

...mit mehreren Zeilen aus einer Datei. Auchtung texte.length für die Länge des Arrays mit allen Zeilen, aber texte[i].length() für die Länge der Zeile mit Nummer i.

size(600, 600);
String[] texte = loadStrings("textdatei.txt");
float zeilenabstand = 60;

background(255);
translate(0, 30);

for (int i=0; i<texte.length; i++) {
  pushMatrix();
  for (int j=0; j<texte[i].length(); j++) {
    textAlign(LEFT, CENTER);
    textSize(random(10, 120));
    fill(random(200));
    text(texte[i].charAt(j), 0, 0);
    translate(textWidth(texte[i].charAt(j)), 0);
  }
  popMatrix();
  translate(0, zeilenabstand);
}

...wobei jedes Zeichen auch einfach als Zahl benutzt werden kann. Zum Beispiel mit einem map(...), das die ASCII-Zahlen (0-127) in Positionen übersetzt.

String[] texte;

size(840, 420);
texte = loadStrings("textdatei.txt");
noFill();

background(255);
translate(20,20);
for (int i=0; i<texte.length; i++) {
  beginShape();
  curveVertex(0,20);
  for (int j=0; j<texte[i].length(); j++) {
    curveVertex(j*6, map(texte[i].charAt(j),49,128,2,20));
  }
  endShape();
  translate(0,40);
}

Fonts als Daten: Schriften manipulieren

Auch auf die Form der Schriften kann man zugreifen. Dabei hilft die Library Geomerative (siehe auch die Beispiele dazu in Generative Gestaltung). Installieren über Sketch/Import Library/Add Library...

Vor der Benutzung mus die Library immer erst initialisiert werden.

import geomerative.*;

size(600,400);
RG.init(this);

Die Library besitzt eigene Variablen für Schriften: RFont, die einen Zugriff auf deren Geometrie erlaubt (allerdings nur für TrueType-Schriften/ttf).

RFont font;
font = new RFont("cour.ttf", 100, RFont.LEFT);

Umwandeln von Text in Geometrie über Funktionen der Library... Ergebnis ist ein Array voller Punkte.

String text = "Form&Code";
RCommand.setSegmentLength(4);
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
RGroup grp;
grp = font.toGroup(text);
grp = grp.toPolygonGroup();
RPoint[] punkte = grp.getPoints();

Zeichnen der Punkte aus dem Array, wie gewohnt:

background(255);
translate(20,height/2);
for (int i=0; i<punkte.length; i++) {
  point(punkte[i].x, punkte[i].y);
}

Insgesamt:

import geomerative.*;

size(600,400);
RG.init(this);
RFont font;
font = new RFont("cour.ttf", 100, RFont.LEFT);

String text = "Form&Code";
RCommand.setSegmentLength(4);
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
RGroup grp;
grp = font.toGroup(text);
grp = grp.toPolygonGroup();
RPoint[] punkte = grp.getPoints();

background(255);
translate(20,height/2);
for (int i=0; i<punkte.length; i++) {
  point(punkte[i].x, punkte[i].y);
}

Jetzt zum Beispiel die einzelnen Punkte zufällig verschieben...

background(255);
strokeWeight(4);
translate(20,height/2);
for (int i=0; i<punkte.length; i++) {
  point(punkte[i].x+random(-2,2), punkte[i].y+random(-2,2));
}

...oder abhängig von der Nummer (i) jedes Punktes.

background(255);
strokeWeight(4);
translate(20,height/2);
for (int i=0; i<punkte.length; i++) {
  float abstand = map(i,0,punkte.length,0,10);
  point(punkte[i].x+random(-abstand,abstand), punkte[i].y+random(-abstand,abstand));
}

Interaktive Schrift

Einmal in Punkte zerlegt, kann die Schrift auch interaktiv manipuliert werden. Dazu zunächst das selbe Programm als Animation (mit setup/draw)...

import geomerative.*;

RFont font;

void setup() {
  size(600, 400);
  RG.init(this);
  font = new RFont("cour.ttf", 100, RFont.LEFT);
}

void draw() {
  String text = "Form&Code";
  RCommand.setSegmentLength(4);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  RGroup grp;
  grp = font.toGroup(text);
  grp = grp.toPolygonGroup();
  RPoint[] punkte = grp.getPoints();

  background(255);
  strokeWeight(4);
  translate(20, height/2);
  for (int i=0; i<punkte.length; i++) {
    float abstand = map(i, 0, punkte.length, 0, 10);
    point(punkte[i].x+random(-abstand, abstand), punkte[i].y+random(-abstand, abstand));
  }
}

...und dann z.B. die Punktgröße von der Maus abhängig machen:

import geomerative.*;

RFont font;

void setup() {
  size(600, 400);
  RG.init(this);
  font = new RFont("cour.ttf", 100, RFont.LEFT);
}

void draw() {
  String text = "Form&Code";
  RCommand.setSegmentLength(4);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  RGroup grp;
  grp = font.toGroup(text);
  grp = grp.toPolygonGroup();
  RPoint[] punkte = grp.getPoints();

  background(255);
  stroke(0,40);
  translate(20, height/2);
  for (int i=0; i<punkte.length; i++) {
    strokeWeight(map(mouseX,0,width,10,60));
    point(punkte[i].x,punkte[i].y);
  }
}

Generative Schriftkontur

Linien zeichnen von Punkt zu Punkt mit Zähler des aktuellen Punktes i und dessen Vorgänger i-1. Weil der erste Punkt (i=0) keinen Vorgänger hat, beginnt die Schleife bei i=1.

import geomerative.*;

size(600,400);
RG.init(this);
RFont font;
font = new RFont("cour.ttf", 100, RFont.LEFT);

String text = "Form&Code";
RCommand.setSegmentLength(4);
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
RGroup grp;
grp = font.toGroup(text);
grp = grp.toPolygonGroup();
RPoint[] punkte = grp.getPoints();

background(255);
translate(20,height/2);
for (int i=1; i<punkte.length; i++) {
  line(punkte[i].x, punkte[i].y,punkte[i-1].x, punkte[i-1].y);
}

Jetzt zum Beispiel zufällig einige Punkte überspringen und in einer eigenen Variable merken, welcher Punkt der letzte war.

import geomerative.*;

size(600,400);
RG.init(this);
RFont font;
font = new RFont("cour.ttf", 100, RFont.LEFT);

String text = "Form&Code";
RCommand.setSegmentLength(4);
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
RGroup grp;
grp = font.toGroup(text);
grp = grp.toPolygonGroup();
RPoint[] punkte = grp.getPoints();

background(255);
translate(20,height/2);
stroke(50);
int letzterPunkt = 0;
for (int i=1; i<punkte.length; i++) {
  line(punkte[i].x, punkte[i].y,punkte[letzterPunkt].x, punkte[letzterPunkt].y);
  letzterPunkt = i;
  i = i + int(random(12));
}

...die interessanten Parameter dieses Programms sind jetzt die 4 in RCommand.setSegmentLength(4), sowie die 12 in i = i + int(random(12)).