Marc Adrian: Computer Texts (1963–1974)

Output

Concept

Marc Adrian's "Computer Texts" are among the earliest works of digital concrete poetry. In this piece, "G-1966-5," words that are spelled identically in German and English (homoglyphs) are placed on a surface in random sizes, orientations, and letter spacing. The dual legibility of the words creates a semantic shimmer between the languages.

The original code was first reimplemented in Python based on Adrian's project sketches (PIL/Pillow). The display here is based on a DOM-based real-time version.

Code

Python
JavaScript
from PIL import Image, ImageDraw, ImageFont
import random
import math

scale_factor = 4
paper_width, paper_height = 29.7 * 10 * scale_factor, 36.3 * 10 * scale_factor
border_offset = 5 * scale_factor

image = Image.new('RGB', (int(paper_width), int(paper_height)), 'white')
draw = ImageDraw.Draw(image)

same_spelling_words = [
    'bitter', 'firm', 'kind', 'band', 'herb', 'boot', 'brat',
    'brave', 'box', 'after', 'chance', 'born', 'die', 'block',
    'also', 'handy', 'brand', 'hell', 'rat', 'halt', 'post',
    'angler', 'fall', 'blind', 'brief', 'hut', 'lager', 'see',
    'taste', 'wink', 'rock'
]

chosen_words = random.choices(same_spelling_words * 2, k=10)

character_distances = [0, 20, 50]
font_sizes = [20, 50, 100]
angles = [0, 0, 0, 15, 30, 45, 60]

font_path = "/Users/Sim/Library/Fonts/LabradorA-Black.ttf"

for word in chosen_words:
    char_distance = random.choice(character_distances)
    font_size = random.choice(font_sizes)
    angle_size = random.choice(angles)
    font = ImageFont.truetype(font_path, font_size)

    if random.choice([True, False]):
        angle_size = -angle_size

    bounding_width = (font_size + char_distance) * len(word)
    bounding_height = font_size

    max_y_offset = (font_size + char_distance) * (len(word) - 1) * math.sin(math.radians(angle_size))

    x_position = random.randint(
        border_offset,
        int(paper_width) - int(bounding_width) - border_offset
    )
    y_position = random.randint(
        border_offset + abs(int(max_y_offset)),
        int(paper_height) - int(bounding_height) - abs(int(max_y_offset)) - border_offset
    )

    for i, char in enumerate(word):
        x_offset = i * (font_size + char_distance) * math.cos(math.radians(angle_size))
        y_offset = i * (font_size + char_distance) * math.sin(math.radians(angle_size))
        draw.text(
            (x_position + x_offset, y_position + y_offset),
            char, font=font, fill="black"
        )

image.save('concrete_poem.png', quality=95)
// Konkrete Poesie nach Marc Adrian — Browser-Portierung
// Platziert deutsch-englische Homographen auf einer Fläche

const WORDS = [
  'bitter', 'firm', 'kind', 'band', 'herb', 'boot', 'brat',
  'brave', 'box', 'after', 'chance', 'born', 'die', 'block',
  'also', 'handy', 'brand', 'hell', 'rat', 'halt', 'post',
  'angler', 'fall', 'blind', 'brief', 'hut', 'lager', 'see',
  'taste', 'wink', 'rock'
];

const CHAR_DISTANCES = [0, 0.3, 0.8];   // em units
const FONT_SIZES    = [14, 28, 48];      // px
const ANGLES        = [0, 0, 0, 15, 30, 45, 60];
const WORD_COUNT    = 10;
const DELAY_MS      = 400;

function pick(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
}

function generate(container) {
  container.innerHTML = '';
  const rect = container.getBoundingClientRect();
  const W = rect.width;
  const H = rect.height;
  const pad = 20;

  const words = Array.from({ length: WORD_COUNT }, () => pick(WORDS));

  words.forEach((word, wi) => {
    const fontSize = pick(FONT_SIZES);
    const charDist = pick(CHAR_DISTANCES);
    let angle = pick(ANGLES);
    if (Math.random() < 0.5) angle = -angle;

    const rad = angle * Math.PI / 180;
    const step = fontSize * (1 + charDist);
    const bw = step * word.length;
    const maxYOff = Math.abs(step * (word.length - 1) * Math.sin(rad));

    const x = pad + Math.random() * Math.max(0, W - bw - 2 * pad);
    const y = pad + maxYOff + Math.random() * Math.max(0, H - fontSize - 2 * maxYOff - 2 * pad);

    for (let i = 0; i < word.length; i++) {
      const span = document.createElement('span');
      span.className = 'gen-char';
      span.textContent = word[i];
      span.style.fontSize = fontSize + 'px';
      span.style.left = (x + i * step * Math.cos(rad)) + 'px';
      span.style.top  = (y + i * step * Math.sin(rad)) + 'px';
      span.style.opacity = '0';
      span.style.transitionDelay = (wi * DELAY_MS + i * 30) + 'ms';
      container.appendChild(span);
    }
  });

  requestAnimationFrame(() => {
    requestAnimationFrame(() => {
      container.querySelectorAll('.gen-char').forEach(el => {
        el.style.opacity = '1';
      });
    });
  });
}