von Patrick Gundlach |

CSS border / Radius und PDF

Ich bin ja gerade dabei, HTML und CSS-Fähigkeiten in den speedata Publisher einzubauen. Dazu gehören die großen Dinge wie Seitenvorlagen und Schriftarten, aber auch die kleinen Details, z.B. CSS Border. Die meist nur bei oberflächlicher Betrachtung einfach sind.

Einfache Fälle

Die HTML-Datei für die Beispiele ist einfach aufgebaut:

<!DOCTYPE html>
<head>
    <link href="mystyle.css" rel="stylesheet">
</head>
<body>
    <span>border</span>
</body>
</html>

mit der dazugehörigen CSS-Datei:

span {
  border: 2px solid green;
}

Der erste »Aha-Effekt« kommt oft, wenn die Rahmen unterschiedliche Farben haben:

span {
  border: 2px solid green;
  border-right-color: yellow;
  border-left-color: blue;
}

Die einzelnen Rahmenstücke bestehen aus Trapezen.

Rundungen

Noch ist die Konstruktion des Rahmens überschaubar. Mit padding erhöht sich der Abstand zwischen dem Inhalt der Box und dem farbigen Rahmen und mit margin erhöht sich der Rand außerhalb des farbigen Rahmens.

Die CSS-Eigenschaften für den Rahmen (border) können aber noch Angaben zum Radius beinhalten, diese geben die Krümmung des Außenradius an. Ich gehe vom einfachen Fall aus, dass nur ein Radius angegeben wird, keine zwei (für die x- und für die y-Achse getrennt).

Der Radius oben rechts beträgt 1pt, also die Hälfte der Rahmendicke

Krümmungen im Rahmen

Was passiert aber, wenn der Krümmungsradius größer ist, als die Strichdicke? Dann muss im Prinzip nur sichergestellt werden, dass die Rahmnendicke überall gegeben ist. Das hört sich einfach an, ist es aber nicht ganz. Folgenden Effekt gibt es, wenn unterschiedliche Rahmendicken benutzt werden:


span {
  border: 1px solid green;
  border-right-color: yellow;
  border-top-right-radius: 9px;
  border-right-width: 7px;
}

Der Rahmen hat plötzlich eine interessante Form. Die Rahmendicke geht über von 9px (rechts) nach 1px oben.

Die Konstruktion des inneren Radius ist, wenn man es einmal verstanden hat, doch recht einfach. Der innere Teil ist eine Ellipse, deren Radien die Differenz des Außenradius und der Strichdicke sind:

Der Außenradius bestimmt sich durch den Kreis, der Innenradius durch die Ellipse. Hier für die obere rechte Ecke und ohne den leeren Innenteil

Ausgabe in PDF

Da mich aber immer die Ausgabe in PDF interessiert, stellt sich die Frage, wie ein ein solcher Rahmen als PDF-Anweisungen erzeugt werden kann.

PDF-Anweisungen sind so etwas wie »Move to position«, »Fill path« oder »Show text«. Also alles, was man im PDF sieht, wird durch solche Anweisungen ausgegeben. In der PDF-Sprache bestehen die Befehle meist aus ein oder zwei Buchstaben.

Die Anweisungen 0.5 w 0 0 m 20 0 l 10 10 l s bedeuten zum Beispiel: setze die Liniendicke auf 0,5 (w), gehe zu Position (0,0) (m), ziehe eine Linie zu (20,0) (l), dann zu (10,0) und schließe die Linie und zeichne den nun entstandenen Pfad mit einem Strich (s). Das Ergebnis ist

Die Zeichnungen in PDF wird immer über solche Pfade realisiert, die auch gefüllt werden oder als »Ausschneidepfad« dienen können. Krümmungen werden über Bézierkurven dargestellt. Mein Vorgehen ist nun, über den farbigen Rahmen zwei Pfade zu legen, deren Äußeres und Inneres weggeschnitten werden:

Der Teil, der beibehalten wird, ist zwischen den beiden dicken Linien.

Alles, was nicht benötigt wird, wird ausgeblendet. Dafür ist der PDF-Befehl 'W' da.

Wichtig ist, den Rahmen nach innen zu vergrößern, da sonst solche komischen Lücken an den Rändern entstehen.

So sieht's richtig aus.

Die farbige Rahmen

Der Code für die vier Trapeze ist noch übersichtlich (das %-Zeichen ist ein Kommentar):

q
  0 w
  % Trapez 1
  0 0.502 0 rg 0 0.502 0 RG
  8 3 m 24.9 3 l 32.9 11 l 0 11 l b
  % Trapez 2
  1 1 0 rg 1 1 0 RG
  24.9 3 m 32.9 -5 l 32.9 11 l 24.9 3 l b
  % Trapez 3
  0 0.502 0 rg 0 0.502 0 RG
  0 -5 m 32.9 -5 l 24.9 3 l 8 3 l b
  % Trapez 4
  0 0 1 rg 0 0 1 RG
  0 -5 m 8 3 l 8 3 l 0 11 l b
Q

q und Q rahmen den Code ein, mit w wird die Liniendicke auf 0 gesetzt, die erste Zeile in jedem Trapez (… rg, … RG) setzen die Farbe, die anderen Befehle zeichnen die Linien (l) und beenden den Pfad und füllen die Fläche (b). Jede Koordinate besteht aus zwei Zahlen (x- und y-Wert).

Die »Ausschneidepfade«

4 -5 m
28.9 -5 l
31.107 -5 32.9 -3.208 32.9 -1 c
32.9 7 l 32.9 9.208 31.107 11 28.9 11 c
4 11 l
1.792 11 0 9.208 0 7 c
0 -1 l
0 -3.208 1.792 -5 4 -5 c

4 -3 m
28.9 -3 l
30.004 -3 30.9 -2.104 30.9 -1 c
30.9 7 l
30.9 8.104 30.004 9 28.9 9 c
4 9 l
2.896 9 2 8.104 2 7 c
2 -1 l
2 -2.104 2.896 -3 4 -3 c
h W* n

Es gibt hier zwei Pfade (außen und innen), die aus 16 Punkten bestehen:

Die Punkte des inneren Pfads werden aus Gründen der Lesbarkeit nicht dargestellt.

Die Punkte 3, 4, 7, 8, 11, 12 und 15,16 sind die Hilfspunkte für die Bézierkurven, die bei dem c-Befehl angegeben werden. Hier ist noch einmal der äußere Pfad mit Kommentaren:

% Punkt 1
4 -5 m

% Punkt 2
28.9 -5 l

% Punkte 3, 4 und 5
31.107 -5 32.9 -3.208 32.9 -1 c

% Punkte 6, 7 und 8
32.9 7 l 32.9 9.208 31.107 11 28.9 11 c

% Punkt 9
4 11 l

% Punkte 10, 11 und 12
1.792 11 0 9.208 0 7 c

% Punkt 13
0 -1 l

% Punkte 14, 15 und 16
0 -3.208 1.792 -5 4 -5 c

Eine Bézierkurve wird in PDF mit drei Punkten erstellt. (Aus der PDF-Spezifikation.)

Nachdem die beiden Pfade erstellt wurden, werden die drei Befehle h W* n ausgeführt. Dadurch wird der Pfad geschlossen (h), als Ausschneidepfad definiert (W*) und beendet, ohne den Pfad zu zeichnen oder zu füllen (n).

Und LuaTeX?

Die Ausgabe mit LuaTeX ist recht einfach. Es wird ein sog. Whatsit erzeugt mit dem Untertyp pdf_literal und in den Ausgabestrom eingefügt. Die beiden langen PDF-Befehlsketten von oben werden dafür aneinander gehängt.

local n_clip = node.new("whatsit","pdf_literal")
-- Zeichenkette der Ausschneidepfade wie oben
-- und angehängt (in Lua mit zwei Punkten: ..)
-- Zeichenkette mit PDF-Befehlen für die Trapeze wie oben
n_clip.data = "4 -5 m ... h W* n " .. "q 0 w ... Q"