FreewarWiki:Bot/Skripts/makemap.php: Unterschied zwischen den Versionen

aus FreewarWiki, der Referenz für Freewar
Zur Navigation springen Zur Suche springen
K (Funktionalität für $draw_grid ergänzt)
(Alles neu macht der Februar)
 
Zeile 1: Zeile 1:
{{Scriptquelltextverwendung}}
{{Scriptquelltextverwendung}}
Letzte Änderungen:
* Umstellung auf "getopt" (Angabe von Optionen auf der Befehlszeile). Neue Features "Atlas" und "Einzelkarten", damit auch Erstellung von Dungeon-Karten. Damit kann man jetzt weitgehend automatisch sowas machen wie http://www.remote-island.org/101912/atlas.pdf (Quelltext: http://www.remote-island.org/101912/atlas.odt) --[[Benutzer:Count Ypsilon|Count Ypsilon]] ([[Benutzer Diskussion:Count Ypsilon|Diskussion]]) 23:24, 17. Feb. 2019 (CET)
<pre>
<pre>
<?php
<?php
header('Content-Type: text/plain; charset=utf-8;');
header('Content-Type: text/plain; charset=utf-8;');


// Bergfeld url
const BERGFELD = 'http://welt1.freewar.de/freewar/images/map/std.jpg';
define("BERGFELD", 'http://welt1.freewar.de/freewar/images/map/std.jpg');
const ATLAS_TEMPLATE = 'atlas-vorlage.odt';
const GESAMTKARTE_LORU = array(2, 2, 170, 400);  
const MAPLIST = 'maplist.txt';
const MAPCACHE = './map_cache';
 
/* ----------------------------------------------------------------------------
* Der Freewar-Kartengenerator
*
*
*/
 
function show_help() {
    echo <<<EOF
 
Befehlszeilenoptionen:
 
--mode          "gesamt": erzeugt eine Gesamtkarte für die Oberfläche
                "einzel": erzeugt einzelne Karten für jedes Gebiet
                "atlas": erzeugt ein Atlas-Dokument (OpenOffice/Libre-
                Office-Format); benötigt dafür eine Vorlage
--dungeons      auch Dungeons mit ausgeben
--grid          alle X Zeilen/Spalten eine Linie
--cellspacing    Pixel Abstand zwischen Feldern
--gridlabels    Gitternetz mit Koordinaten beschriften
--rotatelabels  Koordinaten an X-Achse rotieren
--gebietlabel    Gebiete beschriften (bei Einzel/Atlas)
--labelfont      TTF-Datei für Beschriftungen
--lighten        nichtbetretbare/gebietsfremde Felder aufhellen
--output        Name der Ausgabedatei/des Ausgabeverzeichnisses
--bgcolor        Bildhintergrund in Hex
--verbose        mehr Meldungen anzeigen
 
Bei Modus "Atlas" zusätzlich
--dpi            Auflösung für die Bildausgabe (Default 140, größere Auflösung
                bringt kleinere Bilder)
--pagewidth      druckbare Seitenbreite in cm
--pageheight    druckbare Seitenhöhe in cm
 
 
EOF;
}
 
 
// --- Einlesen der Befehlszeile ----------------------------------------------
 
$longopts = array(
    "mode:",          // gesamt (Default), einzel oder atlas
    "dungeons",        // sollen Dungeons mit ausgegeben werden?
    "verbose",        // soll das Programm geschwätzig sein?
    "lighten",        // sollen gebietsfremde Felde aufgehellt werden?
    "bgcolor:",        // Kartenhintergrund in Web-Notation (Default weiss)
    "grid:",          // Gitternetz-Abstand (Default keins)
    "help",            // Hilfe anzeigen
    "gridlabels",      // soll das Gitternetz beschriftet sein? (Default nein)
    "gebietlabel",    // soll die Gebietskarte ein Label haben? (Default nein)
    "labelfont:",      // TTF-Datei für die Beschriftungen
    "rotatelabels",    // soll X-Achesen-Label 90° rotiert sein? (Default nein)
    "cellspacing:",    // Feld-Abstand (Default 0)
    "dpi:",            // dpi für Bilder im Atlas (Default 140)
    "pagewidth:",      // druckbare Seitenbreite (cm, Papier-Rand) im Atlas
    "pageheight:",    // druckbare Seitenhöhe (cm, Papier-Rand) im Atlas
    "output:",        // Ausgabedatei oder -Verzeichnis
);
 
$o = getopt(NULL, $longopts, $optind);
 
$o['verbose'] = array_key_exists('verbose', $o);
 
if (array_key_exists('help', $o)) {
    show_help();
    exit;
}
 
if (!array_key_exists('mode', $o)) {
    $o['mode'] = 'gesamt';
    echo "Betriebsmodus \"Gesamtkarte\" automatisch gewählt. Programm mit --help\n";
    echo "aufrufen für weitere Funktionen\n";
}
 
if ($o['mode'] != 'gesamt' && $o['mode'] != 'atlas' && $o['mode'] != 'einzel') {
    echo "mode muss entweder 'atlas', 'einzel' oder 'gesamt' sein\n";
    show_help();
    exit;
}


$host  = 'http://www.fwwiki.de';
if (!array_key_exists('bgcolor', $o)) {
$prefix = 'Karte';               // Wiki-Namespace
    $o['bgcolor'] = 'ffffff';
}
 
if (!preg_match('/^[a-f0-9]{6}$/i', $o['bgcolor'])) {
    echo "bgcolor muss eine 6stellige Hexadezimalzahl sein, z.B. fafefa - nicht '".$o['bgcolor']."'\n";
    show_help();
    exit;
}


// pfad zu maplist.txt
if (!array_key_exists('grid', $o)) {
$maplist = 'maplist.txt';
    $o['grid'] = 0;
}


// Pfad zur Ausgabedatei
if (!preg_match('/^\d\d?$/', $o['grid'])) {
$mapfile = './Gesamtkarte (automatisch generiert).jpg';
    echo "grid muss zwischen 0 und 99 liegen\n";
    show_help();
    exit;
}


// pfad zu map_cache
$o['gridlabels'] = array_key_exists('gridlabels', $o);
$map_cache = './map_cache';
if ($o['gridlabels'] && !$o['grid']) {
    echo "gridlabels kann nur zusammen mit grid verwendet werden\n";
    show_help();
    exit;
}


# Bereich angeben. Alles ausserhalb wird ignoriert. Die Karte wird aber
$o['rotatelabels'] = array_key_exists('rotatelabels', $o);
# immer nur so gross, wie tatsaechlich Felder da sind, nicht so gross,
if ($o['rotatelabels'] && !$o['gridlabels']) {
# wie man hier angibt.
    echo "rotatelabels kann nur zusammen mit gridlabels verwendet werden\n";
# (nicht mit 1,1 starten, sonst kriegt man den Dummyplace mit)
    show_help();
$min_x = 2;
    exit;
$min_y = 2;
}
$max_x = 170; # oestlicher Rand, damit Itolos und Belpharia-Inseln draussen bleiben
$max_y = 400;
# Hintergrundfarbe fuer Karte
$bgcolor = "ffffff";


# auf z.b. 5 setzen, wenn felder mit luecken gewuenscht
$o['dungeons'] = array_key_exists('dungeons', $o);
$cellspacing = 0;
# auf 1 setzen, wenn alle 5 zeilen/spalten linie gewuenscht
$draw_grid = 1;


// ab hier nichts ändern ohne Kenntnisse über Funktionsweise des Skripts
$o['gebietlabel'] = array_key_exists('gebietlabel', $o);
function get_templates($template, $wiki_text) {
if ($o['gebietlabel'] && $o['mode'] == 'gesamt') {
    $pattern = '/\{\{(Vorlage:)?' . preg_quote($template, '/') . '/';
    echo "--gebietlabel geht nur bei --mode atlas oder --mode einzel\n";
    show_help();
    exit;
}


    $templates = preg_split($pattern, $wiki_text);
$o['lighten'] = array_key_exists('lighten', $o);
if ($o['lighten'] && $o['mode'] == 'gesamt') {
    echo "--lighten geht nur bei --mode atlas oder --mode einzel\n";
    show_help();
    exit;
}


    return array_slice($templates, 1);
if (!array_key_exists('cellspacing', $o)) $o['cellspacing'] = 0;
if (!preg_match('/^\d\d?$/', $o['cellspacing'])) {
    echo "cellspacing muss zwischen 0 und 99 liegen\n";
    show_help();
    exit;
}
}


// cacht mapfile
if ($o['mode'] == 'atlas') {
function cache_mapfile($url, $map_cache) {
 
     $cache_file = "$map_cache/" . md5($url);
    if (!array_key_exists('dpi', $o)) {
     if (!file_exists($cache_file)) {
        $o['dpi'] = 140;
         file_put_contents($cache_file, file_get_contents($url));
    }
 
    if (!preg_match('/^\d\d\d?$/', $o['dpi'])) {
        echo "dpi muss zwischen 10 und 999 liegen\n";
        show_help();
        exit;
    }
 
    if (!array_key_exists('pagewidth', $o)) $o['pagewidth'] = 12.85;
    $o['pagewidth'] = strtr($o['pagewidth'], ",", ".");
 
    if (!array_key_exists('pageheight', $o)) $o['pageheight'] = 19;
     $o['pageheight'] = strtr($o['pageheight'], ",", ".");
 
    if (!preg_match('/^\d\d?(\.\d+)?$/', $o['pageheight'])) {
        echo "pageheight muss zwischen 0 und 99.9 liegen\n";
        show_help();
        exit;
    }
 
     if (!preg_match('/^\d\d?(\.\d+)?$/', $o['pagewidth'])) {
         echo "pagewidth muss zwischen 0 und 99.9 liegen\n";
        show_help();
        exit;
    }
 
} else {
    if (array_key_exists('dpi', $o) ||
        array_key_exists('pageheight', $o) ||
        array_key_exists('pagewidth', $o)) {
        echo "--dpi, --pageheight, --pagewidth gehen nur mit --mode atlas\n";
        show_help();
        exit;
     }
     }
   
    return $cache_file;
}
}


$parser_function = 'parse_card_article';               // Parser Funktion des Skripts
if (!array_key_exists("labelfont", $o)) {
$api_url         = "$host/api.php?format=json";
    $o['labelfont'] = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf';
}
 
if ($o['gebietlabel'] || $o['gridlabels']) {
    if (!file_exists($o['labelfont'])) {
         echo "Datei ".$o['labelfont']." (--labelfont) nicht gefunden\n";
        show_help();
        exit;
    }
}


// trennzeichen für positionsschlüssel
if (!array_key_exists("output", $o)) {
$pos_delimiter = '|';
    $o['output'] = './karten';
    if ($o['mode'] == 'atlas') $o['output'] = './atlas.odt';
    if ($o['mode'] == 'gesamt' && !$o['dungeons']) $o['output'] = './Gesamtkarte (automatisch generiert).jpg';
}


if ($optind < $argc) {
    echo "Ungültige Befehlszeilenoption\n";
    show_help();
    exit;
}


// Rahmen
// --- Einlesen der Befehlszeile beendet --------------------------------------
$min_x_found = $max_x;
$min_y_found = $max_y;
$max_x_found = $min_x;
$max_y_found = $min_y;
// zusätzliche Felder, die nicht von Feld-Artikeln erfasst werden
$additional_fields = [];


// Bergfeld cachen
if ($o['mode'] == 'atlas') {
cache_mapfile(BERGFELD, $map_cache);


// Karten holen
    // Atlas benötigt eine vorbereitete OpenOffice-Datei
$api_query_url = "$api_url&action=query&list=categorymembers".
    if (!file_exists(ATLAS_TEMPLATE)) {
                        "&cmtitle=Kategorie:Karten&cmtype=page".
        echo "Datei ".ATLAS_TEMPLATE." wird für --mode atlas benötigt, fehlt aber.\n";
                        "&cmprop=ids|sortkeyprefix|title&cmlimit=50";
        echo "Entweder eine leere OpenOffice-Datei erzeugen, die irgendwo den Text\n";
$api_query_url_continue = $api_query_url;
        echo "ADD CONTENT HERE enthält, oder herunterladen von\n";
        echo "http://www.remote-island.org/101912/atlas-vorlage.odt";
        exit;
    }


// pageids der karten
    if ($o['verbose']) echo ATLAS_TEMPLATE.' nach '.$o['output']." kopieren...\n";
$map_pageids = [];
    copy(ATLAS_TEMPLATE, $o['output']);


// kategorieeinträge durchlaufen
    $zipfile = new ZipArchive;
while (true) { 
     $res = $zipfile->open($o['output']);
     $json = json_decode(file_get_contents($api_query_url_continue), true);
     if (!$res) {
      
         echo "Fehler beim Öffnen der Datei ".$o['output']."!\n";
    // pageids hinzufügen
         exit;
    $map_pageids = array_merge($map_pageids, array_filter(array_map(function ($row) {
    }
         // keine ! kategorieeinträge
} else if ($o['mode'] != 'gesamt' || $o['dungeons']) {
         return ($row['sortkeyprefix'][0] == '!') ? null : $row['pageid'];
     mkdir($o['output'], 0777, TRUE);
     }, $json['query']['categorymembers'])));
     if (!is_dir($o['output'])) {
   
         echo "'".$o['output']."' ist kein Verzeichnis bzw. kann nicht angelegt werden!\n";
    // continue url
         exit;
     if (isset($json['continue'])) {
         $api_query_url_continue = "$api_query_url&cmcontinue={$json['continue']['cmcontinue']}";
    } else {
         break;
     }
     }
}
}


// karten durchlaufen
$count = 10000;
$rvlimit = 10;
 
 
// ab hier nichts ändern ohne Kenntnisse über Funktionsweise des Skripts


foreach (array_chunk($map_pageids, $rvlimit) as $pageids) {
// cacht mapfile
     $api_query_url = "$api_url&action=query&prop=revisions".
function cache_mapfile($url) {
                        "&rvprop=content&pageids=". implode("|", $pageids);
     $cache_file = MAPCACHE . "/". md5($url);
     $json = json_decode(file_get_contents($api_query_url), true);
     if (!file_exists($cache_file)) {
        file_put_contents($cache_file, file_get_contents($url));
    }
      
      
     foreach ($json['query']['pages'] as $pageid => $prop) {
     return $cache_file;
        $raw = $prop['revisions'][0]['*'];
       
        // unbetretbare und rahmenfelder erfassen, durchlaufen und an maplist format anpassen
        $additional_fields = array_merge($additional_fields, array_map(function ($template_text) {
            preg_match("/([0-9\-]+)\|([0-9\-]+)(\|([^}\|]+))?/", $template_text, $values);
           
            if (!isset($values[1])) {
                return [];
            }
           
            return [
                // array keys entsprechend format in maplist.txt
                2 => $values[1],
                3 => $values[2],
                5 => isset($values[3]) ? $values[4] : BERGFELD
            ];
        }, array_merge(get_templates("Karte/Unbetretbar", $raw), get_templates("Karte/Rahmenfeld", $raw))));
    }
}
}


// zusätzliche Felder geholt
// trennzeichen für positionsschlüssel
// eigentliche maplist holen und mit zusätzlichen zusammenführen
$pos_delimiter = '|';
$field_rows = array_merge(array_map(function ($line) {
 
     return str_getcsv($line, ";");
// Bergfeld cachen
}, explode("\n", file_get_contents($maplist))), $additional_fields);
cache_mapfile(BERGFELD);
 
// maplist holen
if ($o['verbose']) echo "Felder aus ".MAPLIST." holen und Bilder laden...\n";
 
$handle = fopen(MAPLIST, "r");
if (!$handle) {
     echo "Kann Feldliste aus ".MAPLIST." nicht laden\n";
    exit;
}
$field_rows = array();
while ($data = fgetcsv($handle, 1000, ";")) {
  array_push($field_rows, $data);
}
fclose($handle);


// felder in `position` => `url` format überführen
// felder in `position` => `url` format überführen
$fields = [];
$fields = [];
foreach ($field_rows as $field) {
foreach ($field_rows as $field) {
     # Koordinaten-Check
     $x = array_key_exists(2, $field) ? $field[2] : NULL;
     if ($field[2] < $min_x || $field[2] > $max_x || $field[3] < $min_y || $field[3] > $max_y) {
     $y = array_key_exists(3, $field) ? $field[3] : NULL;
         continue;
    $g = array_key_exists(0, $field) ? $field[0] : NULL;
    $b = array_key_exists(1, $field) ? $field[1] : NULL;
 
    if ($o["mode"] == "gesamt") {
 
        // Für die Gesamtkarte werden die Felder des Kontinents als ein Gebiet
        // behandelt
        if ($x>GESAMTKARTE_LORU[0] && $y>GESAMTKARTE_LORU[1] &&
            $x<GESAMTKARTE_LORU[2] && $y<GESAMTKARTE_LORU[3]) {
            $g="Oberfläche";
        } else {
            // Dungeons (und Außenbereiche des Kontinents wie Narubia)
            // nur, wenn --dungeons gesetzt ist
            if (!$o['dungeons']) continue;
        }
 
    } else {
 
         // Für Einzelkarten und Atlas kann man mit $dungeons steuern,
        // ob auch Dungeons ausgegeben werden sollen
        if (!$o['dungeons'] && $x<2) continue;
     }
     }
     if ($field[2] < $min_x_found) {
 
         $min_x_found = $field[2] ;
    $lim = &$gebiet_limits[$g];
     if (!$lim) {
        $lim["minx"] = 99999;
        $lim["miny"] = 99999;
         $lim["maxx"] = -99999;
        $lim["maxy"] = -99999;
     }
     }
     if ($field[3] < $min_y_found) {
 
         $min_y_found = $field[3];
    // Hier wird die Größe jedes Gebiets ermittelt; dazu berücksichtigen
    }
    // wir aber nur die begehbaren Teile, sonst haben Karten wie z.B.
    if ($field[2] > $max_x_found) {
    // Düsterfrostinsel einen unnötig großen Platzbedarf. TODO, dies
         $max_x_found = $field[2] ;
    // konfigurierbar machen
    }
     if ($b) {
    if ($field[3] > $max_y_found) {
         if ($x < $lim["minx"]) $lim["minx"] = $x;
        $max_y_found = $field[3];
        if ($y < $lim["miny"]) $lim["miny"] = $y;
         if ($x > $lim["maxx"]) $lim["maxx"] = $x;
        if ($y > $lim["maxy"]) $lim["maxy"] = $y;
     }
     }
          
          
     // und gleich bilddatein holen
     // und gleich bilddatein holen
     $cache_file = cache_mapfile($field[5], $map_cache);
     $cache_file = cache_mapfile(array_key_exists(5, $field) ? $field[5] : NULL);
      
      
     // kein Feldtitel oder `Feldtitel Pensal (brennend)`
     // kein Feldtitel oder `Feldtitel Pensal (brennend)`
     if (!isset($field[1]) || strpos($field[1], ' (brennend)') === false) {
     if (!isset($g) || strpos($g, ' (brennend)') === false) {
         $fields["{$field[2]}$pos_delimiter{$field[3]}"] = [
         $fields["{$x}$pos_delimiter{$y}"] = [
             'x' => $field[2],
             'x' => $x,
             'y' => $field[3],
             'y' => $y,
            'g' => $g,
            'begehbar' => $b,
             'file' => $cache_file
             'file' => $cache_file
         ];   
         ];   
Zeile 181: Zeile 353:
                     'x' => $field['x'] + $diff_x,
                     'x' => $field['x'] + $diff_x,
                     'y' => $field['y'] + $diff_y,
                     'y' => $field['y'] + $diff_y,
                     'file' => "$map_cache/" . md5(BERGFELD)
                     'file' => MAPCACHE . '/' . md5(BERGFELD)
                 ];
                 ];
             }
             }
Zeile 190: Zeile 362:
# Groesse eines Kartenfelds feststellen
# Groesse eines Kartenfelds feststellen
list($tilewidth, $tileheight) = getimagesize($cache_file);
list($tilewidth, $tileheight) = getimagesize($cache_file);
echo "tile size: $tilewidth x $tileheight\n";
if ($o['verbose']) echo "tile size: $tilewidth x $tileheight\n";
 
// ein weisses Kartenfeld herstellen
if ($o['lighten']) {
    $aufheller = imagecreatetruecolor($tilewidth, $tileheight);
    $farbe = imagecolorallocate($aufheller, 255, 255, 255);
    imagefilledrectangle($aufheller, 0, 0, $tilewidth, $tileheight, $farbe);
}
 
if ($o["mode"] == 'atlas')
{
  file_put_contents("odt/content.xml", file_get_contents("head.xml"));
  $dungeons = "";
  $oberflaeche = "";
}
 
foreach($gebiet_limits as $gebiet => $limits) {
 
    // Bereich ermitteln
 
    $max_x_found = $limits['maxx'] + 1;
    $max_y_found = $limits['maxy'] + 1;
    $min_x_found = $limits['minx'] - 1;
    $min_y_found = $limits['miny'] - 1;
 
    // Leeres Kartenbild erstellen
 
    $mapwidth = ($max_x_found - $min_x_found + ($o['grid'] ? 3 : 1)) * $tilewidth +
                ($max_x_found - $min_x_found + 2) * $o['cellspacing'];
    $mapheight = ($max_y_found - $min_y_found + ($o['grid'] ? 3 : 1)) * $tileheight +
                ($max_y_found - $min_y_found + 2) * $o['cellspacing'];
 
    if ($o['verbose']) {
        echo "Karte für '$gebiet': x_min: $min_x_found; x_max: $max_x_found; y_min: $min_y_found; y_max: $max_y_found; ";
        echo "Bildgröße: $mapwidth x $mapheight\n";
    }
 
    $mapimage = imagecreatetruecolor($mapwidth, $mapheight);
    if (!$mapimage) {
        echo "Fehler bei der Erstellung des Kartenbilds für '$gebiet'\n";
        continue;
    }
 
    // Hintergrundfarbe setzen
 
    $bgarray = sscanf($o['bgcolor'], "%02X%02X%02X");
    $bgindex = imagecolorallocate($mapimage, $bgarray[0], $bgarray[1], $bgarray[2]);
    imagefill($mapimage, 0, 0, $bgindex);
 
    // Gitternetz einzeichnen und ggf. beschriften
 
    if ($o['grid']) {
        for ($x = $min_x_found; $x <= $max_x_found; $x++) {
            if ($x%$o['grid'] == 0) {
                $mpx = ($x - $min_x_found + 1) * ($tilewidth + $o['cellspacing']) + $o['cellspacing'] + $tilewidth/2;
                imageline($mapimage, $mpx, $o['rotatelabels'] ? 0 : 40, $mpx, $o['rotatelabels'] ? $mapheight : $mapheight - 40, 0);
                if (!$o['gridlabels']) continue;
                $t = $x;
                if (strlen($t) > 4) $t=substr($t,0,2)."\n".substr($t,3);
 
                // X-Achsen-Labels können entweder gerade stehen oder
                // entlang der Achse (rotatelabels)
                if ($o['rotatelabels']) {
                    imagettftext($mapimage, 11, 270, $mpx + 3, 5, 0, $o['labelfont'], $t);
                    $ar = imagettfbbox (11, 270, $o['labelfont'], $t);
                    imagettftext($mapimage, 11, 270, $mpx + 3, $mapheight - $ar[3] - 5, 0, $o['labelfont'], $t);
                } else {
                    $ar = imagettfbbox (11, 0, $o['labelfont'], $t);
                    imagettftext($mapimage, 11, 0, $mpx + 3 - $ar[4]/2, 22 - $ar[5], 0, $o['labelfont'], $t);
                    imagettftext($mapimage, 11, 0, $mpx + 3 - $ar[4]/2, $mapheight + $ar[5] - 10, 0, $o['labelfont'], $t);
                }
            }
        }
        for ($y = $min_y_found; $y <= $max_y_found; $y++) {
            if ($y%$o['grid'] == 0) {
                $mpy = ($y - $min_y_found + 1) * ($tileheight + $o['cellspacing']) + $o['cellspacing'] + $tileheight/2;
                imageline($mapimage, 0, $mpy, $mapwidth, $mpy, 0);
                if (!$o['gridlabels']) continue;
                $t = $y;
                if (strlen($t) > 4) $t=substr($t,0,2)."\n".substr($t,3);
                imagettftext($mapimage, 11, 0, 5, $mpy - 2, 0, $o['labelfont'], $t);
                $ar = imagettfbbox (11, 0, $o['labelfont'], $t);
                imagettftext($mapimage, 11, 0, $mapwidth - 5 - $ar[4], $mpy - 2, 0, $o['labelfont'], $t);
            }
        }
    }
 
    // Label für Gebiet einzeichnen; schwarze Box, darin weisse Box,
    // darin Text
 
    if ($o['gebietlabel']) {
        $ar = imagettfbbox (16, 0, $o['labelfont'], $gebiet);
        // FIXME die Zahlen hier sind etwas magisch durch Ausprobieren
        // gewählt
        imagefilledrectangle($mapimage, $mapwidth - $ar[4] - 55,
            $mapheight + $ar[7] - 26, $mapwidth-51, $mapheight-21, 0);
        imagefilledrectangle($mapimage, $mapwidth - $ar[4] - 54,
            $mapheight + $ar[7] - 25, $mapwidth-52, $mapheight-22, $bgindex);
        imagettftext($mapimage, 16, 0, $mapwidth - $ar[4] - 52,
            $mapheight + $ar[7] - 7, 0, $o['labelfont'], $gebiet);
    }
 
    // Feldbilder an die richtige Stelle im Gebiet einzeichnen.
    // Diese Schleife geht alle Felder durch, die im Rechteck liegen, das
    // das Gebiet umgibt.
    foreach ($fields as $key => $data) {


# Leeres Kartenbild erstellen
        // weiter, wenn das Feld ausserhalb ist
$mapwidth = ($max_x_found - $min_x_found + 3) * $tilewidth +
        if ($data['x'] < $min_x_found) continue;
            ($max_x_found - $min_x_found + 2) * $cellspacing;
        if ($data['x'] > $max_x_found) continue;
$mapheight = ($max_y_found - $min_y_found + 3) * $tileheight +
        if ($data['y'] < $min_y_found) continue;
            ($max_y_found - $min_y_found + 2) * $cellspacing;
        if ($data['y'] > $max_y_found) continue;
$mapimage = imagecreatetruecolor($mapwidth, $mapheight);
echo "x_min: $min_x_found; x_max: $max_x_found; y_min: $min_y_found; y_max: $max_y_found;";
echo "map size: $mapwidth x $mapheight\n";


// hintergrundfarbe
        // Feld kopieren
imagefill($mapimage, 0, 0, imagecolorallocate($mapimage, hexdec(substr($bgcolor, 0, 2)),  
        $offset = ($o['grid']) ? 1 : 0;
                                                        hexdec(substr($bgcolor, 2, 2)),  
        imagecopy($mapimage,  
                                                        hexdec(substr($bgcolor, 4, 2))));
            imagecreatefromjpeg($data['file']),
            ($data['x'] - $min_x_found + $offset) *
                ($tilewidth + $o['cellspacing']) + $o['cellspacing'],  
            ($data['y'] - $min_y_found + $offset) *
                ($tileheight + $o['cellspacing']) + $o['cellspacing'],  
            0, 0,
            $tilewidth, $tileheight);


// Gitter malen, wenn angefordert
        // Feld aufhellen, wenn es nicht zum Gebiet gehört
if ($draw_grid) {
        if ($o['lighten'] && (!array_key_exists('begehbar', $data) || !$data['begehbar'] || $gebiet != $data['g'])) {
    for ($x = $min_x_found; $x <= $max_x_found; $x++) {
            imagecopymerge($mapimage,
        if ($x%5 == 0) {
                $aufheller,
            $mpx = ($x - $min_x_found + 1) * ($tilewidth + $cellspacing) + $cellspacing + $tilewidth/2;
                ($data['x'] - $min_x_found + $offset) *  
            imageline($mapimage, $mpx, 0, $mpx, $mapheight, 0);
                    ($tilewidth + $o['cellspacing']) + $o['cellspacing'],
                ($data['y'] - $min_y_found + $offset) *
                    ($tileheight + $o['cellspacing']) + $o['cellspacing'],
                0, 0,
                $tilewidth, $tileheight,
                50);
         }
         }
     }
     }
     for ($y = $min_y_found; $y <= $max_y_found; $y++) {
 
         if ($y%5 == 0) {
    // Ausgabe des Bildes für ein Gebiet. Im Gesamtkartenmodus gibt es nur
             $mpy = ($y - $min_y_found + 1) * ($tileheight + $cellspacing) + $cellspacing + $tileheight/2;
    // ein Gebiet, also auch nur eine Ausgabe; in den anderen Modi passiert
             imageline($mapimage, 0, $mpy, $mapwidth, $mpy, 0);
    // das hier öfter.
 
     if ($o["mode"] == 'atlas') {
 
        // Für die Atlas-Ausgabe werden Bilder, die nicht auf die Seite
        // passen, eventuell gedreht oder zweigeteilt oder beides. Mehr
        // ist aber nicht drin - wenn das Bild auch gedreht und zweigeteilt
        // nicht passt, fliegt es raus.
 
        $px_per_cm = $o['dpi'] / 2.54;
 
        // Bildgröße in cm
        $w = $mapwidth / $px_per_cm;
        $h = $mapheight / $px_per_cm;
 
        // Bild-Pixel-Spalte, an der wir durchschneiden, wenn nötig
        // (wird gerundet auf ganze Felder, damit wir nicht mitten in
        // einem Feld schneiden)
        $cutoff = floor($o['pagewidth'] * $px_per_cm / ($tilewidth + $o['cellspacing']))
          * ($tilewidth + $o['cellspacing']) - $o['cellspacing']/2 + 2;
 
        // Leeres Array initialisieren; eventuell haben wir mehr als
        // ein Ausgabebild
        $images = array();
 
        if ($w <= $o['pagewidth'] && $h <= $o['pageheight']) {
 
            // alles super, Bild passt auf Seite
            array_push($images, $mapimage);
 
        } elseif ($h <= $o['pagewidth'] && $w <= $o['pageheight']) {
 
            // Bild passt, wenn wir es drehen
            array_push($images, imagerotate($mapimage, 90, $bgindex));
            imagedestroy($mapimage);
 
         } elseif ($w <= 2* $o['pagewidth'] && $h <= $o['pageheight']) {
 
            // Bild passt, wenn wir es durchschneiden - also schneiden
            // und zwei Teile in Ausgabe-Array stecken
            // TODO: Formel 2*pagewidth nicht ganz korrekt, da cutoff
            // bedeutet, dass wir die Seite nicht 100% ausnutzen
            array_push($images, imagecrop($mapimage,
                [ 'x' => 0, 'y' => 0, 'height' => $mapheight, 'width' => $cutoff ]));
             array_push($images, imagecrop($mapimage,
                [ 'x' => $cutoff, 'y' => 0, 'height' => $mapheight,
                    'width' => $mapwidth - $cutoff]));
            imagedestroy($mapimage);
 
        } elseif ($w <= $o['pageheight'] && $h <= 2 * $o['pagewidth']) {
 
            // Bild passt rotiert und geschnitten
            $m = imagerotate($mapimage, 90, $bgindex);
            imagedestroy($mapimage);
            $mapimage = $m;
            $temp = $w; $w = $h; $h = $temp;
             array_push($images, imagecrop($m, [ 'x' => 0, 'y' => 0,  
                'height' => $mapwidth, 'width' => $cutoff ]));
            array_push($images, imagecrop($m, [ 'x' => $cutoff, 'y' => 0,  
                'height' => $mapwidth, 'width' => $mapheight - $cutoff]));
            imagedestroy($m);
 
        } else {
 
            echo "Bild für '$gebiet' zu groß. Wähle höhere DPI oder größeres Papier\n";
 
        }
 
        // Wir führen zwei Ausgabedokumente, eins mit den oberirdischen
        // Gebieten und eins mit den Dungeons:
        if ($min_x_found < 0) {
            $xml = & $dungeons;
        } else {
            $xml = & $oberflaeche;
         }
         }
        // OpenOffice-XML an das passende Dokument anfügen
        $xml .= '<text:h text:style-name="Heading_20_2" text:outline-level="2">'.
            $gebiet."</text:h>";
        foreach ($images as $image) {
            $count++;
            // TODO: Dieser Dateiname muss eigentlich eine Prüfsumme des Inhalts
            // sein. Ist er es nicht, meldet LibreOffice immer ein "defektes
            // Dokument", bietet aber eine Reparatur an.
            ob_start();
            imagepng($image);
            $imagestring = ob_get_clean();
            $imagename = sprintf("Pictures/10000000%08X%08XDEADBEEF%08X.png",
                imagesx($image), imagesy($image), $count);
            $zipfile->addFromString($imagename, $imagestring);
            $h = imagesy($image) / $px_per_cm;
            $w = imagesx($image) / $px_per_cm;
            $xml .= <<<EOF
          <text:p text:style-name="P1">
            <draw:frame draw:style-name="fr1" draw:name="Image$count" text:anchor-type="as-char" svg:width="${w}cm" svg:height="${h}cm" draw:z-index="0">
              <draw:image xlink:href="$imagename" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad" loext:mime-type="image/png"/>
            </draw:frame>
          </text:p>
EOF;
            imagedestroy($image);
        }
    } elseif ($o['mode'] == 'einzel' || ($o['mode'] == 'gesamt' && $o['dungeons'])) {
        // Bei Einzelbildausgabe einfach PNG schreiben
        imagepng($mapimage, $o['output']."/".$gebiet.".png");
        imagedestroy($mapimage);
    } else {
        // Bei Gesamtbild traditionell JPG
        // TODO was wenn ein .png als Ausgabename gewählt wurde?
        imagejpeg($mapimage, $o['output']);
        imagedestroy($mapimage);
     }
     }
}
}
 
if ($o["mode"] == 'atlas')
// Felder einzeichnen
{
foreach ($fields as $key => $data) {
    $old_contents = $zipfile->getFromName('content.xml');
     // bild in map einfügen
    if (!$old_contents) {
     imagecopy($mapimage,
        echo ATLAS_TEMPLATE." enthält keine Datei content.xml - kein .odt-Dokument?\n";
              imagecreatefromjpeg($data['file']),  
     }
              ($data['x'] - $min_x_found + 1) * ($tilewidth + $cellspacing) + $cellspacing,
     if (!preg_match('/(.*)<text:p[^<]*<text:span[^<]*ADD_CONTENT_HERE[^<]*<\/text:span>[^<]*<\/text:p>(.*)/', $old_contents, $matches)) {
              ($data['y'] - $min_y_found + 1) * ($tileheight + $cellspacing) + $cellspacing,  
        if (!preg_match('/(.*)<text:p[^<]*ADD CONTENT HERE[^<]*<\/text:p>(.*)/', $old_contents, $matches)) {
              0, 0,
            echo "Die contents.xml im Atlas-Template entspricht nicht der erwarteten Form.\n";
              $tilewidth, $tileheight);
            echo $old_contents;
            exit;
        }
    }
    $contents = $matches[1] . $oberflaeche . $dungeons . $matches[2];
    $zipfile->addFromString('content.xml', $contents);
    $zipfile->close();
    echo "Atlas erstellt. Die fertige Datei muss nun im OpenOffice/LibreOffice\n";
    echo "geöffnet und \"repariert\" werden.\n";
}
}


// ausgabe
imagejpeg($mapimage, $mapfile);
imagedestroy($mapimage);
</pre>
</pre>

Aktuelle Version vom 18. Februar 2019, 00:24 Uhr

Dieses Script ist hier lediglich archiviert und nicht direkt lauffähig. Wenn Du es benutzen möchtest, musst Du es lokal abspeichern und mit einem geeigneten Interpreter ausführen lassen. Zum Übernehmen solltest Du nicht den unten angezeigten Text verwenden, sondern den Quelltext des Wiki-Artikels: Dazu wählst Du Bearbeiten und kopierst den (meist zwischen PRE-Tags eingefassten) Scripttext.

Sofern Du die Scripte dauerhaft lokal abgespeichert hältst, solltest Du sie vor der nächsten Ausführung darauf prüfen, ob sie noch aktuell sind.

Letzter Bearbeiter: Count Ypsilon — Zuletzt bearbeitet: 18.02.2019

Letzte Änderungen:

<?php
header('Content-Type: text/plain; charset=utf-8;');

const BERGFELD = 'http://welt1.freewar.de/freewar/images/map/std.jpg';
const ATLAS_TEMPLATE = 'atlas-vorlage.odt';
const GESAMTKARTE_LORU = array(2, 2, 170, 400); 
const MAPLIST = 'maplist.txt';
const MAPCACHE = './map_cache';

/* ----------------------------------------------------------------------------
 * Der Freewar-Kartengenerator
 * 
 * 
 */

function show_help() {
    echo <<<EOF

Befehlszeilenoptionen:

--mode           "gesamt": erzeugt eine Gesamtkarte für die Oberfläche
                 "einzel": erzeugt einzelne Karten für jedes Gebiet
                 "atlas": erzeugt ein Atlas-Dokument (OpenOffice/Libre-
                 Office-Format); benötigt dafür eine Vorlage
--dungeons       auch Dungeons mit ausgeben
--grid           alle X Zeilen/Spalten eine Linie
--cellspacing    Pixel Abstand zwischen Feldern
--gridlabels     Gitternetz mit Koordinaten beschriften
--rotatelabels   Koordinaten an X-Achse rotieren
--gebietlabel    Gebiete beschriften (bei Einzel/Atlas)
--labelfont      TTF-Datei für Beschriftungen
--lighten        nichtbetretbare/gebietsfremde Felder aufhellen
--output         Name der Ausgabedatei/des Ausgabeverzeichnisses
--bgcolor        Bildhintergrund in Hex
--verbose        mehr Meldungen anzeigen

Bei Modus "Atlas" zusätzlich
--dpi            Auflösung für die Bildausgabe (Default 140, größere Auflösung
                 bringt kleinere Bilder)
--pagewidth      druckbare Seitenbreite in cm
--pageheight     druckbare Seitenhöhe in cm


EOF;
}


// --- Einlesen der Befehlszeile ----------------------------------------------

$longopts = array(
    "mode:",           // gesamt (Default), einzel oder atlas
    "dungeons",        // sollen Dungeons mit ausgegeben werden?
    "verbose",         // soll das Programm geschwätzig sein?
    "lighten",         // sollen gebietsfremde Felde aufgehellt werden?
    "bgcolor:",        // Kartenhintergrund in Web-Notation (Default weiss)
    "grid:",           // Gitternetz-Abstand (Default keins)
    "help",            // Hilfe anzeigen
    "gridlabels",      // soll das Gitternetz beschriftet sein? (Default nein)
    "gebietlabel",     // soll die Gebietskarte ein Label haben? (Default nein)
    "labelfont:",      // TTF-Datei für die Beschriftungen
    "rotatelabels",    // soll X-Achesen-Label 90° rotiert sein? (Default nein)
    "cellspacing:",    // Feld-Abstand (Default 0)
    "dpi:",            // dpi für Bilder im Atlas (Default 140)
    "pagewidth:",      // druckbare Seitenbreite (cm, Papier-Rand) im Atlas
    "pageheight:",     // druckbare Seitenhöhe (cm, Papier-Rand) im Atlas
    "output:",         // Ausgabedatei oder -Verzeichnis
);

$o = getopt(NULL, $longopts, $optind);

$o['verbose'] = array_key_exists('verbose', $o);

if (array_key_exists('help', $o)) {
    show_help();
    exit;
}

if (!array_key_exists('mode', $o)) {
    $o['mode'] = 'gesamt';
    echo "Betriebsmodus \"Gesamtkarte\" automatisch gewählt. Programm mit --help\n";
    echo "aufrufen für weitere Funktionen\n";
}

if ($o['mode'] != 'gesamt' && $o['mode'] != 'atlas' && $o['mode'] != 'einzel') {
    echo "mode muss entweder 'atlas', 'einzel' oder 'gesamt' sein\n";
    show_help();
    exit;
}

if (!array_key_exists('bgcolor', $o)) {
    $o['bgcolor'] = 'ffffff';
}

if (!preg_match('/^[a-f0-9]{6}$/i', $o['bgcolor'])) {
    echo "bgcolor muss eine 6stellige Hexadezimalzahl sein, z.B. fafefa - nicht '".$o['bgcolor']."'\n";
    show_help();
    exit;
}

if (!array_key_exists('grid', $o)) {
    $o['grid'] = 0;
}

if (!preg_match('/^\d\d?$/', $o['grid'])) {
    echo "grid muss zwischen 0 und 99 liegen\n";
    show_help();
    exit;
}

$o['gridlabels'] = array_key_exists('gridlabels', $o);
if ($o['gridlabels'] && !$o['grid']) {
    echo "gridlabels kann nur zusammen mit grid verwendet werden\n";
    show_help();
    exit;
}

$o['rotatelabels'] = array_key_exists('rotatelabels', $o);
if ($o['rotatelabels'] && !$o['gridlabels']) {
    echo "rotatelabels kann nur zusammen mit gridlabels verwendet werden\n";
    show_help();
    exit;
}

$o['dungeons'] = array_key_exists('dungeons', $o);

$o['gebietlabel'] = array_key_exists('gebietlabel', $o);
if ($o['gebietlabel'] && $o['mode'] == 'gesamt') {
    echo "--gebietlabel geht nur bei --mode atlas oder --mode einzel\n";
    show_help();
    exit;
}

$o['lighten'] = array_key_exists('lighten', $o);
if ($o['lighten'] && $o['mode'] == 'gesamt') {
    echo "--lighten geht nur bei --mode atlas oder --mode einzel\n";
    show_help();
    exit;
}

if (!array_key_exists('cellspacing', $o)) $o['cellspacing'] = 0;
if (!preg_match('/^\d\d?$/', $o['cellspacing'])) {
    echo "cellspacing muss zwischen 0 und 99 liegen\n";
    show_help();
    exit;
}

if ($o['mode'] == 'atlas') {

    if (!array_key_exists('dpi', $o)) {
        $o['dpi'] = 140;
    }

    if (!preg_match('/^\d\d\d?$/', $o['dpi'])) {
        echo "dpi muss zwischen 10 und 999 liegen\n";
        show_help();
        exit;
    }

    if (!array_key_exists('pagewidth', $o)) $o['pagewidth'] = 12.85;
    $o['pagewidth'] = strtr($o['pagewidth'], ",", ".");

    if (!array_key_exists('pageheight', $o)) $o['pageheight'] = 19;
    $o['pageheight'] = strtr($o['pageheight'], ",", ".");

    if (!preg_match('/^\d\d?(\.\d+)?$/', $o['pageheight'])) {
        echo "pageheight muss zwischen 0 und 99.9 liegen\n";
        show_help();
        exit;
    }

    if (!preg_match('/^\d\d?(\.\d+)?$/', $o['pagewidth'])) {
        echo "pagewidth muss zwischen 0 und 99.9 liegen\n";
        show_help();
        exit;
    }

} else {
    if (array_key_exists('dpi', $o) ||
        array_key_exists('pageheight', $o) ||
        array_key_exists('pagewidth', $o)) {
        echo "--dpi, --pageheight, --pagewidth gehen nur mit --mode atlas\n";
        show_help();
        exit;
    }
}

if (!array_key_exists("labelfont", $o)) {
    $o['labelfont'] = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf';
}

if ($o['gebietlabel'] || $o['gridlabels']) {
    if (!file_exists($o['labelfont'])) {
        echo "Datei ".$o['labelfont']." (--labelfont) nicht gefunden\n";
        show_help();
        exit;
    }
}

if (!array_key_exists("output", $o)) {
    $o['output'] = './karten';
    if ($o['mode'] == 'atlas') $o['output'] = './atlas.odt';
    if ($o['mode'] == 'gesamt' && !$o['dungeons']) $o['output'] = './Gesamtkarte (automatisch generiert).jpg';
}

if ($optind < $argc) {
    echo "Ungültige Befehlszeilenoption\n";
    show_help();
    exit;
}

// --- Einlesen der Befehlszeile beendet --------------------------------------

if ($o['mode'] == 'atlas') {

    // Atlas benötigt eine vorbereitete OpenOffice-Datei
    if (!file_exists(ATLAS_TEMPLATE)) {
        echo "Datei ".ATLAS_TEMPLATE." wird für --mode atlas benötigt, fehlt aber.\n";
        echo "Entweder eine leere OpenOffice-Datei erzeugen, die irgendwo den Text\n";
        echo "ADD CONTENT HERE enthält, oder herunterladen von\n";
        echo "http://www.remote-island.org/101912/atlas-vorlage.odt";
        exit;
    }

    if ($o['verbose']) echo ATLAS_TEMPLATE.' nach '.$o['output']." kopieren...\n"; 
    copy(ATLAS_TEMPLATE, $o['output']);

    $zipfile = new ZipArchive;
    $res = $zipfile->open($o['output']);
    if (!$res) {
        echo "Fehler beim Öffnen der Datei ".$o['output']."!\n";
        exit;
    }
} else if ($o['mode'] != 'gesamt' || $o['dungeons']) {
    mkdir($o['output'], 0777, TRUE);
    if (!is_dir($o['output'])) {
        echo "'".$o['output']."' ist kein Verzeichnis bzw. kann nicht angelegt werden!\n";
        exit;
    }
}

$count = 10000;


// ab hier nichts ändern ohne Kenntnisse über Funktionsweise des Skripts

// cacht mapfile
function cache_mapfile($url) {
    $cache_file = MAPCACHE . "/". md5($url);
    if (!file_exists($cache_file)) {
        file_put_contents($cache_file, file_get_contents($url));
    }
    
    return $cache_file;
}

// trennzeichen für positionsschlüssel
$pos_delimiter = '|';

// Bergfeld cachen
cache_mapfile(BERGFELD);

// maplist holen 
if ($o['verbose']) echo "Felder aus ".MAPLIST." holen und Bilder laden...\n";

$handle = fopen(MAPLIST, "r");
if (!$handle) {
    echo "Kann Feldliste aus ".MAPLIST." nicht laden\n";
    exit;
}
$field_rows = array();
while ($data = fgetcsv($handle, 1000, ";")) {
   array_push($field_rows, $data);
}
fclose($handle);

// felder in `position` => `url` format überführen
$fields = [];
foreach ($field_rows as $field) {
    $x = array_key_exists(2, $field) ? $field[2] : NULL;
    $y = array_key_exists(3, $field) ? $field[3] : NULL;
    $g = array_key_exists(0, $field) ? $field[0] : NULL;
    $b = array_key_exists(1, $field) ? $field[1] : NULL;

    if ($o["mode"] == "gesamt") {

        // Für die Gesamtkarte werden die Felder des Kontinents als ein Gebiet
        // behandelt
        if ($x>GESAMTKARTE_LORU[0] && $y>GESAMTKARTE_LORU[1] && 
            $x<GESAMTKARTE_LORU[2] && $y<GESAMTKARTE_LORU[3]) {
            $g="Oberfläche";
        } else {
            // Dungeons (und Außenbereiche des Kontinents wie Narubia)
            // nur, wenn --dungeons gesetzt ist
            if (!$o['dungeons']) continue;
        }

    } else {

        // Für Einzelkarten und Atlas kann man mit $dungeons steuern,
        // ob auch Dungeons ausgegeben werden sollen
        if (!$o['dungeons'] && $x<2) continue;
    }

    $lim = &$gebiet_limits[$g];
    if (!$lim) {
        $lim["minx"] = 99999;
        $lim["miny"] = 99999;
        $lim["maxx"] = -99999;
        $lim["maxy"] = -99999;
    }

    // Hier wird die Größe jedes Gebiets ermittelt; dazu berücksichtigen
    // wir aber nur die begehbaren Teile, sonst haben Karten wie z.B.
    // Düsterfrostinsel einen unnötig großen Platzbedarf. TODO, dies
    // konfigurierbar machen
    if ($b) {
        if ($x < $lim["minx"]) $lim["minx"] = $x;
        if ($y < $lim["miny"]) $lim["miny"] = $y;
        if ($x > $lim["maxx"]) $lim["maxx"] = $x;
        if ($y > $lim["maxy"]) $lim["maxy"] = $y;
    }
        
    // und gleich bilddatein holen
    $cache_file = cache_mapfile(array_key_exists(5, $field) ? $field[5] : NULL);
    
    // kein Feldtitel oder `Feldtitel Pensal (brennend)`
    if (!isset($g) || strpos($g, ' (brennend)') === false) {
        $fields["{$x}$pos_delimiter{$y}"] = [
            'x' => $x,
            'y' => $y,
            'g' => $g,
            'begehbar' => $b,
            'file' => $cache_file
        ];   
    }
    
}

// Randfelder einfügen
foreach ($fields as $field) {
    // Randfelder
    for ($diff_x = -1; $diff_x <= 1; ++$diff_x) {
        for ($diff_y = -1; $diff_y <= 1; ++$diff_y) {
            $edge = ($field['x'] + $diff_x) . $pos_delimiter . ($field['y'] + $diff_y);
            if (!isset($fields[$edge])) {
                $fields[$edge] = [
                    'x' => $field['x'] + $diff_x,
                    'y' => $field['y'] + $diff_y,
                    'file' => MAPCACHE . '/' . md5(BERGFELD)
                ];
            }
        }
    }
}

# Groesse eines Kartenfelds feststellen
list($tilewidth, $tileheight) = getimagesize($cache_file);
if ($o['verbose']) echo "tile size: $tilewidth x $tileheight\n";

// ein weisses Kartenfeld herstellen
if ($o['lighten']) {
    $aufheller = imagecreatetruecolor($tilewidth, $tileheight);
    $farbe = imagecolorallocate($aufheller, 255, 255, 255);
    imagefilledrectangle($aufheller, 0, 0, $tilewidth, $tileheight, $farbe);
}

if ($o["mode"] == 'atlas')
{
   file_put_contents("odt/content.xml", file_get_contents("head.xml"));
   $dungeons = "";
   $oberflaeche = "";
}

foreach($gebiet_limits as $gebiet => $limits) {

    // Bereich ermitteln

    $max_x_found = $limits['maxx'] + 1;
    $max_y_found = $limits['maxy'] + 1;
    $min_x_found = $limits['minx'] - 1;
    $min_y_found = $limits['miny'] - 1;

    // Leeres Kartenbild erstellen

    $mapwidth = ($max_x_found - $min_x_found + ($o['grid'] ? 3 : 1)) * $tilewidth + 
                ($max_x_found - $min_x_found + 2) * $o['cellspacing'];
    $mapheight = ($max_y_found - $min_y_found + ($o['grid'] ? 3 : 1)) * $tileheight + 
                 ($max_y_found - $min_y_found + 2) * $o['cellspacing'];

    if ($o['verbose']) {
        echo "Karte für '$gebiet': x_min: $min_x_found; x_max: $max_x_found; y_min: $min_y_found; y_max: $max_y_found; ";
        echo "Bildgröße: $mapwidth x $mapheight\n";
    }

    $mapimage = imagecreatetruecolor($mapwidth, $mapheight);
    if (!$mapimage) {
        echo "Fehler bei der Erstellung des Kartenbilds für '$gebiet'\n";
        continue;
    }

    // Hintergrundfarbe setzen

    $bgarray = sscanf($o['bgcolor'], "%02X%02X%02X");
    $bgindex = imagecolorallocate($mapimage, $bgarray[0], $bgarray[1], $bgarray[2]);
    imagefill($mapimage, 0, 0, $bgindex);

    // Gitternetz einzeichnen und ggf. beschriften

    if ($o['grid']) {
        for ($x = $min_x_found; $x <= $max_x_found; $x++) {
            if ($x%$o['grid'] == 0) {
                $mpx = ($x - $min_x_found + 1) * ($tilewidth + $o['cellspacing']) + $o['cellspacing'] + $tilewidth/2;
                imageline($mapimage, $mpx, $o['rotatelabels'] ? 0 : 40, $mpx, $o['rotatelabels'] ? $mapheight : $mapheight - 40, 0);
                if (!$o['gridlabels']) continue;
                $t = $x;
                if (strlen($t) > 4) $t=substr($t,0,2)."\n".substr($t,3);

                // X-Achsen-Labels können entweder gerade stehen oder
                // entlang der Achse (rotatelabels)
                if ($o['rotatelabels']) {
                    imagettftext($mapimage, 11, 270, $mpx + 3, 5, 0, $o['labelfont'], $t);
                    $ar = imagettfbbox (11, 270, $o['labelfont'], $t);
                    imagettftext($mapimage, 11, 270, $mpx + 3, $mapheight - $ar[3] - 5, 0, $o['labelfont'], $t);
                } else {
                    $ar = imagettfbbox (11, 0, $o['labelfont'], $t);
                    imagettftext($mapimage, 11, 0, $mpx + 3 - $ar[4]/2, 22 - $ar[5], 0, $o['labelfont'], $t);
                    imagettftext($mapimage, 11, 0, $mpx + 3 - $ar[4]/2, $mapheight + $ar[5] - 10, 0, $o['labelfont'], $t);
                }
            }
        }
        for ($y = $min_y_found; $y <= $max_y_found; $y++) {
            if ($y%$o['grid'] == 0) {
                $mpy = ($y - $min_y_found + 1) * ($tileheight + $o['cellspacing']) + $o['cellspacing'] + $tileheight/2;
                imageline($mapimage, 0, $mpy, $mapwidth, $mpy, 0);
                if (!$o['gridlabels']) continue;
                $t = $y;
                if (strlen($t) > 4) $t=substr($t,0,2)."\n".substr($t,3);
                imagettftext($mapimage, 11, 0, 5, $mpy - 2, 0, $o['labelfont'], $t);
                $ar = imagettfbbox (11, 0, $o['labelfont'], $t);
                imagettftext($mapimage, 11, 0, $mapwidth - 5 - $ar[4], $mpy - 2, 0, $o['labelfont'], $t);
            }
        }
    }

    // Label für Gebiet einzeichnen; schwarze Box, darin weisse Box, 
    // darin Text

    if ($o['gebietlabel']) {
        $ar = imagettfbbox (16, 0, $o['labelfont'], $gebiet);
        // FIXME die Zahlen hier sind etwas magisch durch Ausprobieren
        // gewählt
        imagefilledrectangle($mapimage, $mapwidth - $ar[4] - 55, 
            $mapheight + $ar[7] - 26, $mapwidth-51, $mapheight-21, 0);
        imagefilledrectangle($mapimage, $mapwidth - $ar[4] - 54, 
            $mapheight + $ar[7] - 25, $mapwidth-52, $mapheight-22, $bgindex);
        imagettftext($mapimage, 16, 0, $mapwidth - $ar[4] - 52, 
            $mapheight + $ar[7] - 7, 0, $o['labelfont'], $gebiet);
    }

    // Feldbilder an die richtige Stelle im Gebiet einzeichnen.
    // Diese Schleife geht alle Felder durch, die im Rechteck liegen, das
    // das Gebiet umgibt.
    foreach ($fields as $key => $data) {

        // weiter, wenn das Feld ausserhalb ist
        if ($data['x'] < $min_x_found) continue;
        if ($data['x'] > $max_x_found) continue;
        if ($data['y'] < $min_y_found) continue;
        if ($data['y'] > $max_y_found) continue;

        // Feld kopieren
        $offset = ($o['grid']) ? 1 : 0;
        imagecopy($mapimage, 
            imagecreatefromjpeg($data['file']), 
            ($data['x'] - $min_x_found + $offset) * 
                ($tilewidth + $o['cellspacing']) + $o['cellspacing'], 
            ($data['y'] - $min_y_found + $offset) * 
                ($tileheight + $o['cellspacing']) + $o['cellspacing'], 
            0, 0,
            $tilewidth, $tileheight);

        // Feld aufhellen, wenn es nicht zum Gebiet gehört
        if ($o['lighten'] && (!array_key_exists('begehbar', $data) || !$data['begehbar'] || $gebiet != $data['g'])) {
            imagecopymerge($mapimage,
                $aufheller,
                ($data['x'] - $min_x_found + $offset) * 
                    ($tilewidth + $o['cellspacing']) + $o['cellspacing'],
                ($data['y'] - $min_y_found + $offset) * 
                    ($tileheight + $o['cellspacing']) + $o['cellspacing'],
                0, 0,
                $tilewidth, $tileheight,
                50);
        }

    }

    // Ausgabe des Bildes für ein Gebiet. Im Gesamtkartenmodus gibt es nur 
    // ein Gebiet, also auch nur eine Ausgabe; in den anderen Modi passiert
    // das hier öfter.

    if ($o["mode"] == 'atlas') {

        // Für die Atlas-Ausgabe werden Bilder, die nicht auf die Seite
        // passen, eventuell gedreht oder zweigeteilt oder beides. Mehr
        // ist aber nicht drin - wenn das Bild auch gedreht und zweigeteilt
        // nicht passt, fliegt es raus.

        $px_per_cm = $o['dpi'] / 2.54;

        // Bildgröße in cm
        $w = $mapwidth / $px_per_cm;
        $h = $mapheight / $px_per_cm;

        // Bild-Pixel-Spalte, an der wir durchschneiden, wenn nötig
        // (wird gerundet auf ganze Felder, damit wir nicht mitten in 
        // einem Feld schneiden)
        $cutoff = floor($o['pagewidth'] * $px_per_cm / ($tilewidth + $o['cellspacing'])) 
           * ($tilewidth + $o['cellspacing']) - $o['cellspacing']/2 + 2;

        // Leeres Array initialisieren; eventuell haben wir mehr als
        // ein Ausgabebild
        $images = array();

        if ($w <= $o['pagewidth'] && $h <= $o['pageheight']) {

            // alles super, Bild passt auf Seite
            array_push($images, $mapimage);

        } elseif ($h <= $o['pagewidth'] && $w <= $o['pageheight']) {

            // Bild passt, wenn wir es drehen
            array_push($images, imagerotate($mapimage, 90, $bgindex));
            imagedestroy($mapimage);

        } elseif ($w <= 2* $o['pagewidth'] && $h <= $o['pageheight']) {

            // Bild passt, wenn wir es durchschneiden - also schneiden
            // und zwei Teile in Ausgabe-Array stecken
            // TODO: Formel 2*pagewidth nicht ganz korrekt, da cutoff
            // bedeutet, dass wir die Seite nicht 100% ausnutzen
            array_push($images, imagecrop($mapimage, 
                [ 'x' => 0, 'y' => 0, 'height' => $mapheight, 'width' => $cutoff ]));
            array_push($images, imagecrop($mapimage, 
                [ 'x' => $cutoff, 'y' => 0, 'height' => $mapheight, 
                    'width' => $mapwidth - $cutoff]));
            imagedestroy($mapimage);

        } elseif ($w <= $o['pageheight'] && $h <= 2 * $o['pagewidth']) {

            // Bild passt rotiert und geschnitten
            $m = imagerotate($mapimage, 90, $bgindex);
            imagedestroy($mapimage);
            $mapimage = $m;
            $temp = $w; $w = $h; $h = $temp;
            array_push($images, imagecrop($m, [ 'x' => 0, 'y' => 0, 
                'height' => $mapwidth, 'width' => $cutoff ]));
            array_push($images, imagecrop($m, [ 'x' => $cutoff, 'y' => 0, 
                'height' => $mapwidth, 'width' => $mapheight - $cutoff]));
            imagedestroy($m);

        } else {

             echo "Bild für '$gebiet' zu groß. Wähle höhere DPI oder größeres Papier\n";

        }

        // Wir führen zwei Ausgabedokumente, eins mit den oberirdischen
        // Gebieten und eins mit den Dungeons:
        if ($min_x_found < 0) {
            $xml = & $dungeons;
        } else {
            $xml = & $oberflaeche;
        }

        // OpenOffice-XML an das passende Dokument anfügen
        $xml .= '<text:h text:style-name="Heading_20_2" text:outline-level="2">'.
            $gebiet."</text:h>";
        foreach ($images as $image) {
            $count++;
            // TODO: Dieser Dateiname muss eigentlich eine Prüfsumme des Inhalts
            // sein. Ist er es nicht, meldet LibreOffice immer ein "defektes 
            // Dokument", bietet aber eine Reparatur an.
            ob_start();
            imagepng($image);
            $imagestring = ob_get_clean();
            $imagename = sprintf("Pictures/10000000%08X%08XDEADBEEF%08X.png", 
                imagesx($image), imagesy($image), $count);
            $zipfile->addFromString($imagename, $imagestring);
            $h = imagesy($image) / $px_per_cm;
            $w = imagesx($image) / $px_per_cm;
            $xml .= <<<EOF
          <text:p text:style-name="P1">
            <draw:frame draw:style-name="fr1" draw:name="Image$count" text:anchor-type="as-char" svg:width="${w}cm" svg:height="${h}cm" draw:z-index="0">
              <draw:image xlink:href="$imagename" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad" loext:mime-type="image/png"/>
            </draw:frame>
          </text:p>
EOF;
            imagedestroy($image);
        }

    } elseif ($o['mode'] == 'einzel' || ($o['mode'] == 'gesamt' && $o['dungeons'])) {

        // Bei Einzelbildausgabe einfach PNG schreiben
        imagepng($mapimage, $o['output']."/".$gebiet.".png");
        imagedestroy($mapimage);

    } else {

        // Bei Gesamtbild traditionell JPG
        // TODO was wenn ein .png als Ausgabename gewählt wurde?
        imagejpeg($mapimage, $o['output']);
        imagedestroy($mapimage);

    }
}
if ($o["mode"] == 'atlas')
{
    $old_contents = $zipfile->getFromName('content.xml');
    if (!$old_contents) {
        echo ATLAS_TEMPLATE." enthält keine Datei content.xml - kein .odt-Dokument?\n";
    }
    if (!preg_match('/(.*)<text:p[^<]*<text:span[^<]*ADD_CONTENT_HERE[^<]*<\/text:span>[^<]*<\/text:p>(.*)/', $old_contents, $matches)) {
        if (!preg_match('/(.*)<text:p[^<]*ADD CONTENT HERE[^<]*<\/text:p>(.*)/', $old_contents, $matches)) {
            echo "Die contents.xml im Atlas-Template entspricht nicht der erwarteten Form.\n";
            echo $old_contents;
            exit;
        }
    }
    $contents = $matches[1] . $oberflaeche . $dungeons . $matches[2];
    $zipfile->addFromString('content.xml', $contents);
    $zipfile->close();
    echo "Atlas erstellt. Die fertige Datei muss nun im OpenOffice/LibreOffice\n";
    echo "geöffnet und \"repariert\" werden.\n";
}