word = $word; $this->xMin = $xMin; $this->yMin = $yMin; $this->xMax = $xMax; $this->yMax = $yMax; $this->fontsize = $fontsize; $this->spaceafter = $spaceafter; } } class TextPage { public $words; # array of TextWord objects public $width; # in mm public $height; # in mm public $label; # page number as defined in PDF, or false if not an int function __construct($words, $width, $height, $label) { $this->words = $words; $this->width = $width; $this->height = $height; $this->label = $label; } public function hasLabel() { return $this->label !== false; } public function getPaperType() { $epsilon = 2.0; // in mm if (abs($this->width - DINA4_H) < $epsilon && abs($this->height - DINA4_V) < $epsilon) { return 'A4 Portrait'; } else if (abs($this->width - DINA4_V) < $epsilon && abs($this->height - DINA4_H) < $epsilon) { return 'A4 Landscape'; } else if (abs($this->width - DINA5_H) < $epsilon && abs($this->height - DINA5_V) < $epsilon) { return 'A5 Portrait'; } else if (abs($this->width - DINA5_V) < $epsilon && abs($this->height - DINA5_H) < $epsilon) { return 'A5 Landscape'; } else { return 'Unknown'; } } } function pointsToMillimeters($x) { return 0.352777778 * $x; } function textWordYxCompare($w1, $w2) { $y1 = (int) round(100 * $w1->yMin); $y2 = (int) round(100 * $w2->yMin); if ($y1 < $y2) return -1; else if ($y1 > $y2) return 1; $x1 = $w1->xMin; $x2 = $w2->xMin; if ($x1 < $x2) return -1; else if ($x1 > $x2) return 1; else return 0; } #################################### ## Plagiarism highlighter classes ## #################################### class HighlighterFigure { # It's always a polygon. private $points; private $xMin; private $yMin; private $xMax; private $yMax; public function __construct($points) { $this->points = $points; $this->xMin = 0; $this->yMin = 0; $this->xMax = 0; $this->yMax = 0; foreach (array_values($points) as $i => $point) { if ($i == 0) { $this->xMin = $point[0]; $this->yMin = $point[1]; $this->xMax = $point[0]; $this->yMax = $point[1]; } else { $this->xMin = min($this->xMin, $point[0]); $this->yMin = min($this->yMax, $point[1]); $this->xMax = max($this->xMax, $point[0]); $this->yMax = max($this->xMax, $point[1]); } } } public function getPoints() { return $this->points; } public function getLeft() { return $this->xMin; } public function getTop() { return $this->yMin; } public function getRight() { return $this->xMax; } public function getBottom() { return $this->yMax; } public function getWidth() { return $this->getRight() - $this->getLeft(); } public function getHeight() { return $this->getBottom() - $this->getTop(); } } abstract class Highlighter { abstract public function addWord($textword); abstract public function complete(); private $figures; protected function __construct() { $this->figures = array(); } protected function addFigure($figure) { $this->figures[] = $figure; } protected function addRectFigure($xMin, $yMin, $xMax, $yMax) { $this->figures[] = new HighlighterFigure( array(array($xMin, $yMin), array($xMin, $yMax), array($xMax, $yMax), array($xMax, $yMin))); } public function getFigures() { return $this->figures; } public function getLeft() { $result = 0; foreach ($this->figures as $i => $figure) { if ($i == 0) $result = $figure->getLeft(); else $result = min($result, $figure->getLeft()); } return $result; } public function getTop() { $result = 0; foreach ($this->figures as $i => $figure) { if ($i == 0) $result = $figure->getTop(); else $result = min($result, $figure->getTop()); } return $result; } public function getRight() { $result = 0; foreach ($this->figures as $i => $figure) { if ($i == 0) $result = $figure->getRight(); else $result = max($result, $figure->getRight()); } return $result; } public function getBottom() { $result = 0; foreach ($this->figures as $i => $figure) { if ($i == 0) $result = $figure->getBottom(); else $result = max($result, $figure->getBottom()); } return $result; } public function getWidth() { return $this->getRight() - $this->getLeft(); } public function getHeight() { return $this->getBottom() - $this->getTop(); } } # This Highlighter implementation draws each word as a separate rectangle. class WordHighlighter extends Highlighter { public function addWord($textword) { $this->addRectFigure( $textword->xMin, $textword->yMin, $textword->xMax, $textword->yMax); } public function complete() { } } # This Highlighter implementation combines all matching words into one rect. class BoxHighlighter extends Highlighter { private $count; private $xMin; private $yMin; private $xMax; private $yMax; public function __construct() { $this->count = 0; $this->xMin = 0; $this->yMin = 0; $this->xMax = 0; $this->yMax = 0; } public function addWord($textword) { if ($this->count == 0) { $this->xMin = $textword->xMin; $this->yMin = $textword->yMin; $this->xMax = $textword->xMax; $this->yMax = $textword->yMax; } else { $this->xMin = min($this->xMin, $textword->xMin); $this->yMin = min($this->yMin, $textword->yMin); $this->xMax = max($this->xMax, $textword->xMax); $this->yMax = max($this->yMax, $textword->yMax); } $this->count++; } public function complete() { if ($this->count != 0) { $this->addRectFigure( $this->xMin, $this->yMin, $this->xMax, $this->yMax); } } } # This Highlighter tries to match the layout of the paragraph. class ParagraphHighlighter extends Highlighter { private $count; private $linecount; private $xMin; private $yMin; private $xMax; private $yMax; private $xFirst; private $xLast; private $ySecondLineA; private $ySecondLineB; private $yPrevLineA; private $yPrevLineB; public function __construct() { $this->reset(); } private function reset() { $this->count = 0; $this->linecount = 0; $this->xMin = 0; $this->yMin = 0; $this->xMax = 0; $this->yMax = 0; $this->xFirst = 0; $this->xLast = 0; $this->ySecondLineA = 0; $this->ySecondLineB = 0; $this->yPrevLineA = 0; $this->yPrevLineB = 0; } public function addWord($textword) { $xMin = $textword->xMin; $yMin = $textword->yMin; $xMax = $textword->xMax; $yMax = $textword->yMax; # assert we get words in top-down, left-right order $isNewLine = false; if ($this->count != 0) { $epsilon = 0.01; // in mm assert($yMin >= $this->yMin - $epsilon); if ($yMin < $this->yMin) { $yMin = $this->yMin; } else if ($yMin > $this->yMax + $epsilon) { $isNewLine = true; } else { assert($xMin >= $this->xMin - $epsilon); if ($xMin < $this->xLast) $xMin = $this->xLast; } } if ($xMax < $xMin) $xMax = $xMin; if ($yMax < $yMin) $yMax = $yMin; # start a new paragraph when y distance is large enough # hint about the formula: 2*ym - yp == ym + (ym - yp) if ($this->count > 0 && $yMin >= 2 * $this->yMax - $this->yPrevLineB) { $this->complete(); } if ($this->count == 0) { # initialize a new shape $this->count = 1; $this->linecount = 1; $this->xMin = $xMin; $this->yMin = $yMin; $this->xMax = $xMax; $this->yMax = $yMax; $this->xFirst = $xMin; $this->xLast = $xMax; $this->ySecondLineA = $yMax; $this->ySecondLineB = $yMax; $this->yPrevLineA = $yMin; $this->yPrevLineB = $yMin; } else { # add the word to an existing shape $this->count++; if ($isNewLine) { # add it to a new line of the existing shape $this->linecount++; if ($this->linecount == 2) { $this->ySecondLineA = $this->yMax; $this->ySecondLineB = $yMin; } $this->yPrevLineA = $this->yMax; $this->yPrevLineB = $yMin; } $this->xLast = $xMax; $this->xMin = min($this->xMin, $xMin); $this->yMin = min($this->yMin, $yMin); $this->xMax = max($this->xMax, $xMax); $this->yMax = max($this->yMax, $yMax); } } public function complete() { # produce better alignment when there are >= 2 lines if ($this->linecount >= 2) { $ySecondLine = 0.5 * ($this->ySecondLineA + $this->ySecondLineB); $yPrevLine = 0.5 * ($this->yPrevLineA + $this->yPrevLineB); $this->yMin -= max(0, $this->ySecondLineB - $ySecondLine); $this->yMax += max(0, $this->yPrevLineB - $yPrevLine); } if ($this->linecount == 0) { # no plagiarism here :( } else if ($this->linecount == 1) { # simple rectangular shape $this->addRectFigure( $this->xMin, $this->yMin, $this->xMax, $this->yMax); } else if ($this->linecount == 2 && $this->xLast < $this->xFirst) { # double rectangular shape, example: # lorem ipsum # dolor sit amet $this->addRectFigure( $this->xFirst, $this->yMin, $this->xMax, $ySecondLine); $this->addRectFigure( $this->xMin, $ySecondLine, $this->xLast, $this->yMax); } else { # complex paragraph shape, example: # lorem ipsum dolor # sit amet, consectetur adipisicing elit $this->addFigure(new HighlighterFigure( array(array($this->xFirst, $this->yMin), array($this->xFirst, $ySecondLine), array($this->xMin, $ySecondLine), array($this->xMin, $this->yMax), array($this->xLast, $this->yMax), array($this->xLast, $yPrevLine), array($this->xMax, $yPrevLine), array($this->xMax, $this->yMin)))); } $this->reset(); } } ###################### ## String functions ## ###################### function removeNeedleFootnotes($s) { if (preg_match("/^(.*?)\r?\n\r?\n\[(Fn\.?\s*)?[0-9]+\]/musi", $s, $match)) { return $match[1]; } else { return $s; } } function removeTextInBrackets($s, $open, $close) { # Can't use preg_replace here because we need context-free matching. $result = ''; $depth = 0; $pos = 0; while ($pos < strlen($s)) { $openpos = strpos($s, $open, $pos); $openpos_or_end = ($openpos === false) ? strlen($s) : $openpos; $closepos = strpos($s, $close, $pos); $closepos_or_end = ($closepos === false) ? strlen($s) : $closepos; if ($depth == 0) { $result .= substr($s, $pos, min($openpos_or_end, $closepos_or_end) - $pos); } if ($openpos === false && $closepos === false) { $pos = strlen($s); } else if ($openpos_or_end < $closepos_or_end) { $depth++; $pos = $openpos+1; } else { if ($depth > 0) $depth--; $pos = $closepos+1; } } return $result; } function splitWords($s) { # note the /u for unicode support return preg_split('/\W+/u', $s, -1, PREG_SPLIT_NO_EMPTY); } ########################################## ## Text / fragment processing functions ## ########################################## function parseTextPageFragments($filename) { $result = array(); $xml = new SimpleXMLElement(file_get_contents($filename)); for ($i = 0; isset($xml->pages->page[$i]); ++$i) { $xmlpage = $xml->pages->page[$i]; if (is_numeric((string) $xmlpage['label'])) { $pagelabel = (int) $xmlpage['label']; } else { $pagelabel = false; } $pagewords = array(); for ($j = 0; isset($xmlpage->word[$j]); ++$j) { $xmlword = $xmlpage->word[$j]; $pagewords[] = new TextWord( (string) $xmlword, pointsToMillimeters((float) $xmlword['xMin']), pointsToMillimeters((float) $xmlword['yMin']), pointsToMillimeters((float) $xmlword['xMax']), pointsToMillimeters((float) $xmlword['yMax']), (float) $xmlword['fontsize'], ($xmlword['spaceafter'] == 'true')); } usort($pagewords, 'textWordYxCompare'); $result[] = new TextPage( $pagewords, pointsToMillimeters((float) $xmlpage['width']), pointsToMillimeters((float) $xmlpage['height']), $pagelabel); } return $result; } function cleanupTextPageFragments($textpages) { foreach ($textpages as $textpage) { # Combine words that pdftotext mistakenly reported as separate. for ($i = 0; $i < count($textpage->words) - 1;) { $word = $textpage->words[$i]; $next = $textpage->words[$i+1]; if ($word->spaceafter && abs($word->yMin - $next->yMin) < 0.1 && abs($word->yMax - $next->yMax) < 0.1 && abs($word->fontsize - $next->fontsize) < 0.1 && $next->xMin > $word->xMax && $next->xMin < $word->xMax + 0.05*$word->fontsize) { #print "Combining \"".$word->word."\" and \"".$next->word."\" on page ".$textpage->label."\n"; $word->word .= $next->word; $word->xMax = $next->xMax; $word->spaceafter = $next->spaceafter; unset($textpage->words[$i+1]); $textpage->words = array_values($textpage->words); } else { ++$i; } } } } function getPDFPageNums($textpages) { $result = array(); foreach ($textpages as $i => $textpage) { if ($textpage->hasLabel()) $result[$textpage->label] = $i; } return $result; } function getTextWordsXMax($textwords) { $result = 0; foreach ($textwords as $textword) { $result = max($result, $textword->xMax); } return $result; } function joinTextWords($textwords) { $result = ''; $space = ' '; foreach ($textwords as $textword) { if ($result !== '') $result .= $space; $result .= $textword->word; $space = $textword->spaceafter ? ' ' : "\n"; } return $result; } function filterTextWords($textwords, $only_footnotes) { $result = array(); foreach ($textwords as $textword) { if (($textword->fontsize >= FOOTNOTE_THRESHOLD) ^ ($only_footnotes)) { $result[] = $textword; } } return $result; } function kochMehrinMustFilter($textpage) { return False; # return $textpage->label != 58 && $textpage->label != 59 # && $textpage->label != 69; } function kochMehrinMustScale($textpage) { return False; # return $textpage->getPaperType() != "A5 Portrait"; } function kochMehrinScale($textwords, $textpage) { return $textwords; # $result = array(); # if ($textpage->label % 2 == 0) { # $xshift = -39.5; # $yshift = -52.0; # } else { # $xshift = -31.5; # $yshift = -50.5; # } # foreach ($textwords as $textword) { # $newtextword = clone $textword; # $newtextword->xMin += $xshift; # $newtextword->yMin += $yshift; # $newtextword->xMax += $xshift; # $newtextword->yMax += $yshift; # $result[] = $newtextword; # } # return $result; } function longestCommonSubsequence($x, $y) { # Longest Common Subsequence algorithm # See http://www.algorithmist.com/index.php/Longest_Common_Subsequence # $x and $y must be arrays of strings; elements of these are considered # equal if their levenshtein distance is below LEVENSHTEIN_THRESHOLD # returns two lists of indices (into $x and $y, respectively) $n = count($x); $m = count($y); $table = array(); for ($i = 0; $i <= $n; ++$i) { for ($j = 0; $j <= $m; ++$j) { if ($i == 0 || $j == 0) { $table[$i][$j] = 0; } else if (levenshtein($x[$i-1], $y[$j-1]) <= LEVENSHTEIN_THRESHOLD) { $table[$i][$j] = $table[$i-1][$j-1] + 1; } else { $table[$i][$j] = max($table[$i-1][$j], $table[$i][$j-1]); } } } $recon_x = array(); $recon_y = array(); $i = $n; $j = $m; $tablemax = $table[$n][$m]; while ($i > 0 && $j > 0) { if (2*$table[$i][$j] < $tablemax && levenshtein($x[$i-1],$y[$j-1]) <= LEVENSHTEIN_THRESHOLD) { # When half the subsequence has been reconstructed, # combine words more aggressively. # This avoids cases likes: # $x: *a* text string with a *duplicate* *word* # $y: *a* *duplicate* *word* # Note that we want the second "a" in $x to be # combined with the first "a" in $y instead. $recon_x[] = $i-1; $recon_y[] = $j-1; $i--; $j--; } else if ($table[$i-1][$j] == $table[$i][$j]) { --$i; } else if ($table[$i][$j-1] == $table[$i][$j]) { --$j; } else { $recon_x[] = $i-1; $recon_y[] = $j-1; $i--; $j--; } } $recon_x = array_reverse($recon_x); $recon_y = array_reverse($recon_y); #print "recon_x: ".implode(" ",$recon_x)."\n"; #print "recon_y: ".implode(" ",$recon_y)."\n"; return array($recon_x, $recon_y); } function lookForSimilarText($textpage, $linestart, $lineend, $needle) { #print "-----------------------------------------------\n"; # Remove unneeded text fragments from the haystack $haystack = $textpage->words; # FIXME: Koch-Mehrin.pdf is very broken, this code # handles the problem it somewhat... if (kochMehrinMustFilter($textpage)) { if ($linestart <= 100) { # Looking for non-footnotes, remove footnote words $haystack = filterTextWords($haystack, false); } else { # Looking only for footnotes, remove non-footnote words $haystack = filterTextWords($haystack, true); } } if (kochMehrinMustScale($textpage)) { $haystack = kochMehrinScale($haystack, $textpage); } #print joinTextWords($haystack)."\n"; # Split text fragments into words and remember their indices $haystack_words = array(); $haystack_wordtoindex_min = array(); $haystack_wordtoindex_max = array(); $haystack_joinwords = false; # set to true when line ends with a hyphen foreach ($haystack as $wordindex => $textword) { if ($textword->yMin > PAGENUM_THRESHOLD && $textword->fontsize > FOOTNOTE_THRESHOLD && (int) $textword->word === $textpage->label) { # Assume this word is the page number. Ignore. continue; } foreach (splitWords($textword->word) as $textword_subword) { if ($haystack_joinwords) { $haystack_words[count($haystack_words)-1] .= $textword_subword; $haystack_wordtoindex_max[count($haystack_wordtoindex_max)-1] = $wordindex; $haystack_joinwords = false; } else { $haystack_words[] = $textword_subword; $haystack_wordtoindex_min[] = $wordindex; $haystack_wordtoindex_max[] = $wordindex; } } if (!$textword->spaceafter && preg_match('/-$/', $textword->word)) { $haystack_joinwords = true; } } # Remove text in angular or square brackets from the needle text if ($linestart <= 100 && $lineend <= 100) { $needle = removeNeedleFootnotes($needle); } $needle = removeTextInBrackets($needle, '[', ']'); $needle = removeTextInBrackets($needle, '<', '>'); # Split needle into words $needle_words = splitWords($needle); #print "Haystack: ".implode(" ",$haystack_words)."\n"; #print "Needle: ".implode(" ",$needle_words)."\n"; # Find longest common subsequence between haystack and needle list($recon_haystack, $recon_needle) = longestCommonSubsequence($haystack_words, $needle_words); if (2*count($recon_needle) < count($needle_words)) return false; // no (long enough) common subsequence found # Mark matching haystack fragments as being used foreach ($recon_haystack as $i) { for ($j = $haystack_wordtoindex_min[$i]; $j <= $haystack_wordtoindex_max[$i]; ++$j) { $haystack_fragused[$j] = $haystack[$j]; } } # collect matching haystack fragments #$result = array(); #foreach ($haystack as $wordindex => $textword) { # if ($haystack_fragused[$wordindex]) # $result[] = $textword; #} $result = new ParagraphHighlighter(); foreach ($haystack_fragused as $wordindex => $textword) { $result->addWord($textword); } $result->complete(); #print "-----------------------------------------------\n"; return $result; } ################## ## Main program ## ################## # Cache laden if(!file_exists('cache')) { print "Fehler: Cache existiert nicht! 'make cache' ausgefuehrt?\n"; exit(1); } $cache = unserialize(file_get_contents('cache')); # Liste ignorierter Fragmente/Plagiatskategorien/Quellen anzeigen foreach($cache['ignored']['fragments'] as $title) { print "%XXX: Ignoriere Fragment: $title\n"; } foreach($cache['ignored']['fragmenttypes'] as $title) { print "%XXX: Ignoriere Plagiatskategorie: $title\n"; } foreach($cache['ignored']['sources'] as $title) { print "%XXX: Ignoriere Quelle: $title\n"; } # Liste der Quellen erzeugen $cache_sources = array(); foreach($cache['sources'] as $source) { $source_color = $COLORMAP[count($cache_sources)]; $source_color = preg_replace('/.*=/', '', $source_color); $source['color'] = $source_color; $cache_sources[$source['title']] = $source; } # Liste der Plagiatskategorien holen $cache_fragtypes = array(); foreach($cache['fragmenttypes'] as $fragtype) { $cache_fragtypes[$fragtype['title']] = $fragtype; } # Textpositionen laden if(!file_exists('0140.xml')) { print "Fehler: 0140.xml existiert nicht!\n"; exit(1); } $textpages = parseTextPageFragments("0140.xml"); cleanupTextPageFragments($textpages); $pdfpagenums = getPDFPageNums($textpages); # Fragmente verarbeiten $per_page_textblocks = array(); $used_sources = array(); foreach ($cache['fragments'] as $f) { $frag_wikititle = $f['wikiTitle']; print "Processing $frag_wikititle\n"; if (preg_match('/^\s*(\d+)\s*$/', $f[1], $match)) { $frag_realpagenum = (int) $match[1]; } else { print "Error: $frag_wikititle: Unable to parse page number!\n"; continue; } if (preg_match('/^\s*(\d+)\s*$/', $f[2], $match)) { $frag_linestart = (int) $match[1]; $frag_lineend = (int) $match[1]; } else if (preg_match('/^\s*(\d+)\s*-\s*(\d+)\s*$/', $f[2], $match)) { $frag_linestart = (int) $match[1]; $frag_lineend = (int) $match[2]; } else { print "Error: $frag_wikititle: Unable to parse line numbers!\n"; continue; } if ($frag_linestart > 100 ^ $frag_lineend > 100) { print "Error: $frag_wikititle: Line numbers span footnote boundary!\n"; continue; } if ($frag_linestart == $frag_lineend) { $expected_wikititle = sprintf("Skm/Fragment %03d %02d", $frag_realpagenum, $frag_linestart); } else { $expected_wikititle = sprintf("Skm/Fragment %03d %02d-%02d", $frag_realpagenum, $frag_linestart, $frag_lineend); } if ($frag_wikititle != $expected_wikititle) { print "Warning: $frag_wikititle: wikititle seems wrong, expected: $expected_wikititle\n"; } $frag_plagtext = $f[3]; $frag_src_page = $f[4]; $frag_src_line = $f[5]; $frag_src_line = preg_replace('/^-*$/', '', $frag_src_line); $frag_types = array_values(array_intersect($f['categories'], array_keys($cache_fragtypes))); if(empty($frag_types)) { print "Warning: $frag_wikititle: No fragment type category found! (".implode(", ", $f['categories']).")\n"; } else if(count($frag_types) >= 2) { print "Warning: $frag_wikititle: Multiple fragment type categories found! (".implode(", ", $f['categories']).")\n"; } $frag_type_category = ($frag_types ? $frag_types[0] : ''); $frag_type = preg_replace('/^Kategorie:/', '', $frag_type_category); if ($frag_type == 'Kein Plagiat' || $frag_type == 'Verdchtig') { #print "Warning: $frag_wikititle: Ignored, type = $frag_type\n"; continue; } $frag_sources = array_values(array_intersect($f['categories'], array_keys($cache_sources))); if(empty($frag_sources)) { print "Warning: $frag_wikititle: No source category found! (".implode(", ", $f['categories']).")\n"; } else if(count($frag_sources) >= 2) { print "Warning: $frag_wikititle: Multiple source categories found! (".implode(", ", $f['categories']).")\n"; } $frag_source_category = ($frag_sources ? $frag_sources[0] : ''); $frag_source = preg_replace('/^Kategorie:/', '', $frag_source_category); # find corresponding PDF page number if (!isset($pdfpagenums[$frag_realpagenum])) { print "Error: $frag_wikititle: page number $frag_realpagenum not found in PDF\n"; continue; } $frag_pdfpagenum = $pdfpagenums[$frag_realpagenum]; if (!isset($textpages[$frag_pdfpagenum])) { print "Error: $frag_wikititle: PDF page $frag_pdfpagenum does not contain OCR text fragments\n"; continue; } # Find correspondences $correspondence = lookForSimilarText($textpages[$frag_pdfpagenum], $frag_linestart, $frag_lineend, $frag_plagtext); if ($correspondence === false || !$correspondence->getFigures()) { print "Error: $frag_wikititle: Unable to find correspondence!\n"; continue; } # Create textblock that shows the original author $textblock_hpos = $correspondence->getRight() + 2; $textblock_vpos = $correspondence->getTop(); $textblock_hsize = DINA5_H - $textblock_hpos; $textblock = sprintf("\\begin{textblock}{%d}(%d,%d)\n", $textblock_hsize, $textblock_hpos, $textblock_vpos); $textblock .= "\\fontsize{6}{7}\\selectfont\n"; if ($frag_source != '') { $textblock .= "$frag_source\\\\\n"; } else { $textblock .= "Unbekannte Quelle\\\\\n"; } if ($frag_src_page != '') { if ($frag_src_line != '') { $textblock .= "S. $frag_src_page (Z. $frag_src_line)\\\\\n"; } else { $textblock .= "S. $frag_src_page\\\\\n"; } } else if ($frag_src_line != '') { $textblock .= "Z. $frag_src_line\\\\\n"; } if ($frag_source != '') { $textblock .= "$frag_type\n"; } $textblock .= "\\end{textblock}\n"; $per_page_textblocks[$frag_pdfpagenum][] = $textblock; # Create colored highlights if (isset($cache_sources[$frag_source_category])) { $textblock_color = $cache_sources[$frag_source_category]['color']; } else { $textblock_color = '255,0,0'; } $textblock_alpha = 0.3; foreach ($correspondence->getFigures() as $figure) { $textblock_hpos = $figure->getLeft(); $textblock_hsize = $figure->getWidth(); $textblock_vpos = $figure->getTop(); $textblock_vsize = $figure->getHeight(); $textblock = sprintf("\\begin{textblock}{%d}(%d,%d)\n", $textblock_hsize, $textblock_hpos, $textblock_vpos); $textblock .= "\\begin{tikzpicture}\n"; $textblock .= sprintf("\\definecolor{fragcol}{RGB}{%s}\n", $textblock_color); $textblock .= sprintf("\\draw[fill=fragcol,opacity=%f] ", $textblock_alpha); foreach ($figure->getPoints() as $i => $point) { $textblock .= sprintf("(%.3f,%.3f) -- ", $point[0]/10.0, ($textblock_vpos-$point[1])/10.0); } $textblock .= "cycle;\n"; $textblock .= "\\end{tikzpicture}\n"; $textblock .= "\\end{textblock}\n"; $per_page_textblocks[$frag_pdfpagenum][] = $textblock; } $textblock = sprintf("\\begin{textblock}{%d}(%d,%d)\n", $textblock_hsize, $textblock_hpos, $textblock_vpos); # XXX $textblock = "\\begin{tikzpicture}[remember picture, overlay]\n"; $textblock .= sprintf("\\definecolor{fragcol}{RGB}{10,10,60}\n", $textblock_color); $textblock .= sprintf("\\filldraw[fill=fragcol,opacity=0.8,even odd rule] "); $textblock .= "(current page.north west) -- (current page.north east) -- (current page.south east) -- (current page.south west) -- cycle\n"; foreach ($correspondence->getFigures() as $figure) { $textblock_hpos = $figure->getLeft(); $textblock_hsize = $figure->getWidth(); $textblock_vpos = $figure->getTop(); $textblock_vsize = $figure->getHeight(); # $textblock .= sprintf("\n XX (%.3f,%.3f,%.3f,%.3f,%.3f,%.3f)\n", $textblock_hpos/10.0, $textblock_vpos/10.0, $textblock_hsize/10.0, $textblock_vsize/10.0, $point[0]/10.0, $point[1]/10.0); # foreach ($figure->getPoints() as $i => $point) { # $textblock .= sprintf("(%.3f,%.3f) -- ", ($point[0])/10.0, ($textblock_vpos+$textblock_vsize-$point[1])/10.0); # } $textblock .= sprintf ("(%.3f,%.3f) rectangle (%.3f,%.3f);", $textblock_hpos/10.0, $textblock_vpos/10.0, $textblock_hsize/10.0, $textblock_vsize/10.0); $textblock .= "cycle\n"; } $textblock .= "; \\end{tikzpicture}\n"; $per_page_textblocks[$frag_pdfpagenum][] = $textblock; # Remember which sources are referenced -- for the title page $frag_source_tilde = str_replace(' ', '~', $frag_source); if ($frag_source != '' && !in_array($frag_source_tilde, $used_sources)) $used_sources[] = $frag_source_tilde; } # Add the title and subtitle textblock on page 1 $textblock_hpos = 8; $textblock_hsize = DINA5_H - 2 * $textblock_hpos - 30; $textblock_vpos = 23; $textblock = sprintf("\\begin{textblock}{%d}(%d,%d)\n", $textblock_hsize, $textblock_hpos, $textblock_vpos); sort($used_sources); $textblock .= "\\fontsize{8}{9}\\selectfont\n"; $textblock .= "und: ".implode(', ', $used_sources)."\n"; $textblock .= "\\end{textblock}\n"; $per_page_textblocks[0][] = $textblock; $textblock_hpos = 8; $textblock_hsize = DINA5_H - 2 * $textblock_hpos - 30; $textblock_vpos = 120; $textblock = sprintf("\\begin{textblock}{%d}(%d,%d)\n", $textblock_hsize, $textblock_hpos, $textblock_vpos); $textblock .= "\\center{\\Huge{}VroniPlag - Kritische Edition vom \\today}\n"; $textblock .= "\\end{textblock}\n"; $per_page_textblocks[0][] = $textblock; # Write output to overlay-file.tex $file = fopen("overlay-file2.tex", "w"); fwrite($file, "\\documentclass[10pt,a4paper]{article}\n"); fwrite($file, "\\usepackage[ngerman]{babel}\n"); #fwrite($file, "\\usepackage[absolute,overlay,showboxes]{textpos}\n"); fwrite($file, "\\usepackage[absolute,overlay]{textpos}\n"); fwrite($file, "\\usepackage[T1]{fontenc}\n"); fwrite($file, "\\usepackage[utf8]{inputenc}\n"); fwrite($file, "\\usepackage[condensed,math]{iwona}\n"); fwrite($file, "\\usepackage{tikz}\n"); fwrite($file, "\\setlength{\\parindent}{0pt}\n"); fwrite($file, "\\setlength{\\TPHorizModule}{1mm}\n"); fwrite($file, "\\setlength{\\TPVertModule}{\\TPHorizModule}\n"); fwrite($file, "\\pagestyle{empty}\n"); fwrite($file, "\\begin{document}\n"); $max_pdfpagenum = max(array_keys($per_page_textblocks)); for ($pdfpagenum = 0; $pdfpagenum <= $max_pdfpagenum; ++$pdfpagenum) { if (isset($per_page_textblocks[$pdfpagenum])) { fwrite($file, implode('', $per_page_textblocks[$pdfpagenum])); } #if ($pdfpagenum != $max_pdfpagenum) { fwrite($file, "\\hphantom{1em}\\newpage"); #} } fwrite($file, "\\hphantom{1em}\n"); fwrite($file, "\\end{document}\n"); fclose($file);