All pastes #2066040 Raw Edit

Someone

public text v1 · immutable
#2066040 ·published 2011-05-20 23:42 UTC
rendered paste body
<?php

# must be set according to the codeset the input XML file is in
setlocale(LC_ALL, 'en_US.utf8');

# font size below which an OCR fragment is considered to be a footnote
define('FOOTNOTE_THRESHOLD', 8.0);

# min y position at which an OCR fragment may be considered a page number
define('PAGENUM_THRESHOLD', 190.0);

# max dissimilarity for words to be considered the same
define('LEVENSHTEIN_THRESHOLD', 0);

# width of DinA4 page, in millimeters
define('DINA4_H', 210.0);

# height of DinA4 page, in millimeters
define('DINA4_V', 297.0);

# width of DinA4 page, in millimeters
define('DINA5_H', 148.0);

# height of DinA4 page, in millimeters
define('DINA5_V', 210.0);


###################
## Source colors ##
###################

$COLORMAP = array(
	"medium forest green=50,129,75",
	"crimson=220,20,60",
	"teal=0,128,128",
	"chartreuse=127,255,0",
	"midnight blue=25,25,112",
	"navy=0,0,128",
	"light coral=240,128,128",
	"tomato=255,99,71",
	"orange red=255,69,0",
	"cornflower blue=100,149,237",
	"burlywood=222,184,135",
	"dark slate blue=72,61,139",
	"medium orchid=186,85,211",
	"medium blue=0,0,205",
	"royal blue=65,105,225",
	"blue=0,0,255",
	"dodger blue=30,144,255",
	"deep sky blue=0,191,255",
	"sky blue=135,206,235",
	"light sky blue=135,206,250",
	"steel blue=70,130,180",
	"light steel blue=176,196,222",
	"light blue=173,216,230",
	"powder blue=176,224,230",
	"pale turquoise=175,238,238",
	"dark turquoise=0,206,209",
	"medium turquoise=72,209,204",
	"turquoise=64,224,208",
	"cyan=0,255,255",
	"light cyan=224,255,255",
	"LightCyan=224,255,255",
	"cadet blue=95,158,160",
	"CadetBlue=95,158,160",
	"medium aquamarine=102,205,170",
	"aquamarine=127,255,212",
	"dark green=0,100,0",
	"dark olive green=85,107,47",
	"dark sea green=143,188,143",
	"sea green=46,139,87",
	"medium sea green=60,179,113",
	"light sea green=32,178,170",
	"pale green=152,251,152",
	"spring green=0,255,127",
	"lawn green=124,252,0",
	"green=0,255,0",
	"medium spring green=0,250,154",
	"green yellow=173,255,47",
	"lime green=50,205,50",
	"yellow green=154,205,50",
	"forest green=34,139,34",
	"olive drab=107,142,35",
	"dark khaki=189,183,107",
	"khaki=240,230,140",
	"pale goldenrod=238,232,170",
	"light goldenrod yellow=250,250,210",
	"light yellow=255,255,224",
	"yellow=255,255,0",
	"gold=255,215,0",
	"light goldenrod=238,221,130",
	"goldenrod=218,165,32",
	"dark goldenrod=184,134,11",
	"rosy brown=188,143,143",
	"indian red=205,92,92",
	"saddle brown=139,69,19",
	"sienna=160,82,45",
	"peru=205,133,63",
	"beige=245,245,220",
	"wheat=245,222,179",
	"sandy brown=244,164,96",
	"tan=210,180,140",
	"chocolate=210,105,30",
	"firebrick=178,34,34",
	"brown=165,42,42",
	"dark salmon=233,150,122",
	"salmon=250,128,114",
	"light salmon=255,160,122",
	"orange=255,165,0",
	"dark orange=255,140,0",
	"coral=255,127,80",
	"slate blue=106,90,205",
	"medium slate blue=123,104,238",
	"light slate blue=132,112,255",
	"red=255,0,0",
	"hot pink=255,105,180",
	"deep pink=255,20,147",
	"pink=255,192,203",
	"light pink=255,182,193",
	"pale violet red=219,112,147",
	"maroon=176,48,96",
	"medium violet red=199,21,133",
	"violet red=208,32,144",
	"magenta=255,0,255",
	"violet=238,130,238",
	"plum=221,160,221",
	"orchid=218,112,214",
	"dark orchid=153,50,204",
	"dark violet=148,0,211",
	"blue violet=138,43,226",
	"purple=160,32,240",
	"medium purple=147,112,219",
	"thistle=216,191,216",
	"snow=255,250,250",
	"ghost white=248,248,255",
	"white smoke=245,245,245",
	"gainsboro=220,220,220",
	"floral white=255,250,240",
	"old lace=253,245,230",
	"linen=250,240,230",
	"antique white=250,235,215",
	"papaya whip=255,239,213",
	"blanched almond=255,235,205",
	"bisque=255,228,196",
	"peach puff=255,218,185",
	"navajo white=255,222,173",
	"moccasin=255,228,181",
	"cornsilk=255,248,220",
	"ivory=255,255,240",
	"lemon chiffon=255,250,205",
	"seashell=255,245,238",
	"honeydew=240,255,240",
	"mint cream=245,255,250",
	"azure=240,255,255",
	"alice blue=240,248,255",
	"lavender=230,230,250",
	"lavender blush=255,240,245",
	"misty rose=255,228,225",
	"white=255,255,255",
	"black=0,0,0",
	"dark slate gray=47,79,79",
	"dark slate grey=47,79,79",
	"dim gray=105,105,105",
	"dim grey=105,105,105",
	"slate gray=112,128,144",
	"slate grey=112,128,144",
	"light slate gray=119,136,153",
	"light slate grey=119,136,153",
	"gray=190,190,190",
	"grey=190,190,190",
	"light grey=211,211,211",
	"light gray=211,211,211",
	"snow1=255,250,250",
	"snow2=238,233,233",
	"snow3=205,201,201",
	"snow4=139,137,137",
	"seashell1=255,245,238",
	"seashell2=238,229,222",
	"seashell3=205,197,191",
	"seashell4=139,134,130",
	"AntiqueWhite1=255,239,219",
	"AntiqueWhite2=238,223,204",
	"AntiqueWhite3=205,192,176",
	"AntiqueWhite4=139,131,120",
	"bisque1=255,228,196",
	"bisque2=238,213,183",
	"bisque3=205,183,158",
	"bisque4=139,125,107",
	"PeachPuff1=255,218,185",
	"PeachPuff2=238,203,173",
	"PeachPuff3=205,175,149",
	"PeachPuff4=139,119,101",
	"NavajoWhite1=255,222,173",
	"NavajoWhite2=238,207,161",
	"NavajoWhite3=205,179,139",
	"NavajoWhite4=139,121,94",
	"LemonChiffon1=255,250,205",
	"LemonChiffon2=238,233,191",
	"LemonChiffon3=205,201,165",
	"LemonChiffon4=139,137,112",
	"cornsilk1=255,248,220",
	"cornsilk2=238,232,205",
	"cornsilk3=205,200,177",
	"cornsilk4=139,136,120",
	"ivory1=255,255,240",
	"ivory2=238,238,224",
	"ivory3=205,205,193",
	"ivory4=139,139,131",
	"honeydew1=240,255,240",
	"honeydew2=224,238,224",
	"honeydew3=193,205,193",
	"honeydew4=131,139,131",
	"LavenderBlush1=255,240,245",
	"LavenderBlush2=238,224,229",
	"LavenderBlush3=205,193,197",
	"LavenderBlush4=139,131,134",
	"MistyRose1=255,228,225",
	"MistyRose2=238,213,210",
	"MistyRose3=205,183,181",
	"MistyRose4=139,125,123",
	"azure1=240,255,255",
	"azure2=224,238,238",
	"azure3=193,205,205",
	"azure4=131,139,139",
	"SlateBlue1=131,111,255",
	"SlateBlue2=122,103,238",
	"SlateBlue3=105,89,205",
	"SlateBlue4=71,60,139",
	"RoyalBlue1=72,118,255",
	"RoyalBlue2=67,110,238",
	"RoyalBlue3=58,95,205",
	"RoyalBlue4=39,64,139",
	"blue1=0,0,255",
	"blue2=0,0,238",
	"blue3=0,0,205",
	"blue4=0,0,139",
	"DodgerBlue1=30,144,255",
	"DodgerBlue2=28,134,238",
	"DodgerBlue3=24,116,205",
	"DodgerBlue4=16,78,139",
	"SteelBlue1=99,184,255",
	"SteelBlue2=92,172,238",
	"SteelBlue3=79,148,205",
	"SteelBlue4=54,100,139",
	"DeepSkyBlue1=0,191,255",
	"DeepSkyBlue2=0,178,238",
	"DeepSkyBlue3=0,154,205",
	"DeepSkyBlue4=0,104,139",
	"SkyBlue1=135,206,255",
	"SkyBlue2=126,192,238",
	"SkyBlue3=108,166,205",
	"SkyBlue4=74,112,139",
	"LightSkyBlue1=176,226,255",
	"LightSkyBlue2=164,211,238",
	"LightSkyBlue3=141,182,205",
	"LightSkyBlue4=96,123,139",
	"SlateGray1=198,226,255",
	"SlateGray2=185,211,238",
	"SlateGray3=159,182,205",
	"SlateGray4=108,123,139",
	"LightSteelBlue1=202,225,255",
	"LightSteelBlue2=188,210,238",
	"LightSteelBlue3=162,181,205",
	"LightSteelBlue4=110,123,139",
	"LightBlue1=191,239,255",
	"LightBlue2=178,223,238",
	"LightBlue3=154,192,205",
	"LightBlue4=104,131,139",
	"LightCyan1=224,255,255",
	"LightCyan2=209,238,238",
	"LightCyan3=180,205,205",
	"LightCyan4=122,139,139",
	"PaleTurquoise1=187,255,255",
	"PaleTurquoise2=174,238,238",
	"PaleTurquoise3=150,205,205",
	"PaleTurquoise4=102,139,139",
	"CadetBlue1=152,245,255",
	"CadetBlue2=142,229,238",
	"CadetBlue3=122,197,205",
	"CadetBlue4=83,134,139",
	"turquoise1=0,245,255",
	"turquoise2=0,229,238",
	"turquoise3=0,197,205",
	"turquoise4=0,134,139",
	"cyan1=0,255,255",
	"cyan2=0,238,238",
	"cyan3=0,205,205",
	"cyan4=0,139,139",
	"DarkSlateGray1=151,255,255",
	"DarkSlateGray2=141,238,238",
	"DarkSlateGray3=121,205,205",
	"DarkSlateGray4=82,139,139",
	"aquamarine1=127,255,212",
	"aquamarine2=118,238,198",
	"aquamarine3=102,205,170",
	"aquamarine4=69,139,116",
	"DarkSeaGreen1=193,255,193",
	"DarkSeaGreen2=180,238,180",
	"DarkSeaGreen3=155,205,155",
	"DarkSeaGreen4=105,139,105",
	"SeaGreen1=84,255,159",
	"SeaGreen2=78,238,148",
	"SeaGreen3=67,205,128",
	"SeaGreen4=46,139,87",
	"PaleGreen1=154,255,154",
	"PaleGreen2=144,238,144",
	"PaleGreen3=124,205,124",
	"PaleGreen4=84,139,84",
	"SpringGreen1=0,255,127",
	"SpringGreen2=0,238,118",
	"SpringGreen3=0,205,102",
	"SpringGreen4=0,139,69",
	"green1=0,255,0",
	"green2=0,238,0",
	"green3=0,205,0",
	"green4=0,139,0",
	"chartreuse1=127,255,0",
	"chartreuse2=118,238,0",
	"chartreuse3=102,205,0",
	"chartreuse4=69,139,0",
	"OliveDrab1=192,255,62",
	"OliveDrab2=179,238,58",
	"OliveDrab3=154,205,50",
	"OliveDrab4=105,139,34",
	"DarkOliveGreen1=202,255,112",
	"DarkOliveGreen2=188,238,104",
	"DarkOliveGreen3=162,205,90",
	"DarkOliveGreen4=110,139,61",
	"khaki1=255,246,143",
	"khaki2=238,230,133",
	"khaki3=205,198,115",
	"khaki4=139,134,78",
	"LightGoldenrod1=255,236,139",
	"LightGoldenrod2=238,220,130",
	"LightGoldenrod3=205,190,112",
	"LightGoldenrod4=139,129,76",
	"LightYellow1=255,255,224",
	"LightYellow2=238,238,209",
	"LightYellow3=205,205,180",
	"LightYellow4=139,139,122",
	"yellow1=255,255,0",
	"yellow2=238,238,0",
	"yellow3=205,205,0",
	"yellow4=139,139,0",
	"gold1=255,215,0",
	"gold2=238,201,0",
	"gold3=205,173,0",
	"gold4=139,117,0",
	"goldenrod1=255,193,37",
	"goldenrod2=238,180,34",
	"goldenrod3=205,155,29",
	"goldenrod4=139,105,20",
	"DarkGoldenrod1=255,185,15",
	"DarkGoldenrod2=238,173,14",
	"DarkGoldenrod3=205,149,12",
	"DarkGoldenrod4=139,101,8",
	"RosyBrown1=255,193,193",
	"RosyBrown2=238,180,180",
	"RosyBrown3=205,155,155",
	"RosyBrown4=139,105,105",
	"IndianRed1=255,106,106",
	"IndianRed2=238,99,99",
	"IndianRed3=205,85,85",
	"IndianRed4=139,58,58",
	"sienna1=255,130,71",
	"sienna2=238,121,66",
	"sienna3=205,104,57",
	"sienna4=139,71,38",
	"burlywood1=255,211,155",
	"burlywood2=238,197,145",
	"burlywood3=205,170,125",
	"burlywood4=139,115,85",
	"wheat1=255,231,186",
	"wheat2=238,216,174",
	"wheat3=205,186,150",
	"wheat4=139,126,102",
	"tan1=255,165,79",
	"tan2=238,154,73",
	"tan3=205,133,63",
	"tan4=139,90,43",
	"chocolate1=255,127,36",
	"chocolate2=238,118,33",
	"chocolate3=205,102,29",
	"chocolate4=139,69,19",
	"firebrick1=255,48,48",
	"firebrick2=238,44,44",
	"firebrick3=205,38,38",
	"firebrick4=139,26,26",
	"brown1=255,64,64",
	"brown2=238,59,59",
	"brown3=205,51,51",
	"brown4=139,35,35",
	"salmon1=255,140,105",
	"salmon2=238,130,98",
	"salmon3=205,112,84",
	"salmon4=139,76,57",
	"LightSalmon1=255,160,122",
	"LightSalmon2=238,149,114",
	"LightSalmon3=205,129,98",
	"LightSalmon4=139,87,66",
	"orange1=255,165,0",
	"orange2=238,154,0",
	"orange3=205,133,0",
	"orange4=139,90,0",
	"DarkOrange1=255,127,0",
	"DarkOrange2=238,118,0",
	"DarkOrange3=205,102,0",
	"DarkOrange4=139,69,0",
	"coral1=255,114,86",
	"coral2=238,106,80",
	"coral3=205,91,69",
	"coral4=139,62,47",
	"tomato1=255,99,71",
	"tomato2=238,92,66",
	"tomato3=205,79,57",
	"tomato4=139,54,38",
	"OrangeRed1=255,69,0",
	"OrangeRed2=238,64,0",
	"OrangeRed3=205,55,0",
	"OrangeRed4=139,37,0",
	"red1=255,0,0",
	"red2=238,0,0",
	"red3=205,0,0",
	"red4=139,0,0",
	"DebianRed=215,7,81",
	"DeepPink1=255,20,147",
	"DeepPink2=238,18,137",
	"DeepPink3=205,16,118",
	"DeepPink4=139,10,80",
	"HotPink1=255,110,180",
	"HotPink2=238,106,167",
	"HotPink3=205,96,144",
	"HotPink4=139,58,98",
	"pink1=255,181,197",
	"pink2=238,169,184",
	"pink3=205,145,158",
	"pink4=139,99,108",
	"LightPink1=255,174,185",
	"LightPink2=238,162,173",
	"LightPink3=205,140,149",
	"LightPink4=139,95,101",
	"PaleVioletRed1=255,130,171",
	"PaleVioletRed2=238,121,159",
	"PaleVioletRed3=205,104,137",
	"PaleVioletRed4=139,71,93",
	"maroon1=255,52,179",
	"maroon2=238,48,167",
	"maroon3=205,41,144",
	"maroon4=139,28,98",
	"VioletRed1=255,62,150",
	"VioletRed2=238,58,140",
	"VioletRed3=205,50,120",
	"VioletRed4=139,34,82",
	"magenta1=255,0,255",
	"magenta2=238,0,238",
	"magenta3=205,0,205",
	"magenta4=139,0,139",
	"orchid1=255,131,250",
	"orchid2=238,122,233",
	"orchid3=205,105,201",
	"orchid4=139,71,137",
	"plum1=255,187,255",
	"plum2=238,174,238",
	"plum3=205,150,205",
	"plum4=139,102,139",
	"MediumOrchid1=224,102,255",
	"MediumOrchid2=209,95,238",
	"MediumOrchid3=180,82,205",
	"MediumOrchid4=122,55,139",
	"DarkOrchid1=191,62,255",
	"DarkOrchid2=178,58,238",
	"DarkOrchid3=154,50,205",
	"DarkOrchid4=104,34,139",
	"purple1=155,48,255",
	"purple2=145,44,238",
	"purple3=125,38,205",
	"purple4=85,26,139",
	"MediumPurple1=171,130,255",
	"MediumPurple2=159,121,238",
	"MediumPurple3=137,104,205",
	"MediumPurple4=93,71,139",
	"thistle1=255,225,255",
	"thistle2=238,210,238",
	"thistle3=205,181,205",
	"thistle4=139,123,139",
	"gray0=0,0,0",
	"grey0=0,0,0",
	"gray1=3,3,3",
	"grey1=3,3,3",
	"gray2=5,5,5",
	"grey2=5,5,5",
	"gray3=8,8,8",
	"grey3=8,8,8",
	"gray4=10,10,10",
	"grey4=10,10,10",
	"gray5=13,13,13",
	"grey5=13,13,13",
	"gray6=15,15,15",
	"grey6=15,15,15",
	"gray7=18,18,18",
	"grey7=18,18,18",
	"gray8=20,20,20",
	"grey8=20,20,20",
	"gray9=23,23,23",
	"grey9=23,23,23",
	"gray10=26,26,26",
	"grey10=26,26,26",
	"gray11=28,28,28",
	"grey11=28,28,28",
	"gray12=31,31,31",
	"grey12=31,31,31",
	"gray13=33,33,33",
	"grey13=33,33,33",
	"gray14=36,36,36",
	"grey14=36,36,36",
	"gray15=38,38,38",
	"grey15=38,38,38",
	"gray16=41,41,41",
	"grey16=41,41,41",
	"gray17=43,43,43",
	"grey17=43,43,43",
	"gray18=46,46,46",
	"grey18=46,46,46",
	"gray19=48,48,48",
	"grey19=48,48,48",
	"gray20=51,51,51",
	"grey20=51,51,51",
	"gray21=54,54,54",
	"grey21=54,54,54",
	"gray22=56,56,56",
	"grey22=56,56,56",
	"gray23=59,59,59",
	"grey23=59,59,59",
	"gray24=61,61,61",
	"grey24=61,61,61",
	"gray25=64,64,64",
	"grey25=64,64,64",
	"gray26=66,66,66",
	"grey26=66,66,66",
	"gray27=69,69,69",
	"grey27=69,69,69",
	"gray28=71,71,71",
	"grey28=71,71,71",
	"gray29=74,74,74",
	"grey29=74,74,74",
	"gray30=77,77,77",
	"grey30=77,77,77",
	"gray31=79,79,79",
	"grey31=79,79,79",
	"gray32=82,82,82",
	"grey32=82,82,82",
	"gray33=84,84,84",
	"grey33=84,84,84",
	"gray34=87,87,87",
	"grey34=87,87,87",
	"gray35=89,89,89",
	"grey35=89,89,89",
	"gray36=92,92,92",
	"grey36=92,92,92",
	"gray37=94,94,94",
	"grey37=94,94,94",
	"gray38=97,97,97",
	"grey38=97,97,97",
	"gray39=99,99,99",
	"grey39=99,99,99",
	"gray40=102,102,102",
	"grey40=102,102,102",
	"gray41=105,105,105",
	"grey41=105,105,105",
	"gray42=107,107,107",
	"grey42=107,107,107",
	"gray43=110,110,110",
	"grey43=110,110,110",
	"gray44=112,112,112",
	"grey44=112,112,112",
	"gray45=115,115,115",
	"grey45=115,115,115",
	"gray46=117,117,117",
	"grey46=117,117,117",
	"gray47=120,120,120",
	"grey47=120,120,120",
	"gray48=122,122,122",
	"grey48=122,122,122",
	"gray49=125,125,125",
	"grey49=125,125,125",
	"gray50=127,127,127",
	"grey50=127,127,127",
	"gray51=130,130,130",
	"grey51=130,130,130",
	"gray52=133,133,133",
	"grey52=133,133,133",
	"gray53=135,135,135",
	"grey53=135,135,135",
	"gray54=138,138,138",
	"grey54=138,138,138",
	"gray55=140,140,140",
	"grey55=140,140,140",
	"gray56=143,143,143",
	"grey56=143,143,143",
	"gray57=145,145,145",
	"grey57=145,145,145",
	"gray58=148,148,148",
	"grey58=148,148,148",
	"gray59=150,150,150",
	"grey59=150,150,150",
	"gray60=153,153,153",
	"grey60=153,153,153",
	"gray61=156,156,156",
	"grey61=156,156,156",
	"gray62=158,158,158",
	"grey62=158,158,158",
	"gray63=161,161,161",
	"grey63=161,161,161",
	"gray64=163,163,163",
	"grey64=163,163,163",
	"gray65=166,166,166",
	"grey65=166,166,166",
	"gray66=168,168,168",
	"grey66=168,168,168",
	"gray67=171,171,171",
	"grey67=171,171,171",
	"gray68=173,173,173",
	"grey68=173,173,173",
	"gray69=176,176,176",
	"grey69=176,176,176",
	"gray70=179,179,179",
	"grey70=179,179,179",
	"gray71=181,181,181",
	"grey71=181,181,181",
	"gray72=184,184,184",
	"grey72=184,184,184",
	"gray73=186,186,186",
	"grey73=186,186,186",
	"gray74=189,189,189",
	"grey74=189,189,189",
	"gray75=191,191,191",
	"grey75=191,191,191",
	"gray76=194,194,194",
	"grey76=194,194,194",
	"gray77=196,196,196",
	"grey77=196,196,196",
	"gray78=199,199,199",
	"grey78=199,199,199",
	"gray79=201,201,201",
	"grey79=201,201,201",
	"gray80=204,204,204",
	"grey80=204,204,204",
	"gray81=207,207,207",
	"grey81=207,207,207",
	"gray82=209,209,209",
	"grey82=209,209,209",
	"gray83=212,212,212",
	"grey83=212,212,212",
	"gray84=214,214,214",
	"grey84=214,214,214",
	"gray85=217,217,217",
	"grey85=217,217,217",
	"gray86=219,219,219",
	"grey86=219,219,219",
	"gray87=222,222,222",
	"grey87=222,222,222",
	"gray88=224,224,224",
	"grey88=224,224,224",
	"gray89=227,227,227",
	"grey89=227,227,227",
	"gray90=229,229,229",
	"grey90=229,229,229",
	"gray91=232,232,232",
	"grey91=232,232,232",
	"gray92=235,235,235",
	"grey92=235,235,235",
	"gray93=237,237,237",
	"grey93=237,237,237",
	"gray94=240,240,240",
	"grey94=240,240,240",
	"gray95=242,242,242",
	"grey95=242,242,242",
	"gray96=245,245,245",
	"grey96=245,245,245",
	"gray97=247,247,247",
	"grey97=247,247,247",
	"gray98=250,250,250",
	"grey98=250,250,250",
	"gray99=252,252,252",
	"grey99=252,252,252",
	"gray100=255,255,255",
	"grey100=255,255,255",
	"dark grey=169,169,169",
	"DarkGrey=169,169,169",
	"dark gray=169,169,169",
	"DarkGray=169,169,169",
	"dark blue=0,0,139",
	"DarkBlue=0,0,139",
	"dark cyan=0,139,139",
	"DarkCyan=0,139,139",
	"dark magenta=139,0,139",
	"DarkMagenta=139,0,139",
	"dark red=139,0,0",
	"DarkRed=139,0,0",
	"light green=144,238,144",
	"LightGreen=144,238,144",
);


##########################################
## OCR text fragments (words and pages) ##
##########################################

class TextWord {
	public $word;        # the word text (utf8)
	public $xMin;        # in mm
	public $yMin;        # in mm
	public $xMax;        # in mm
	public $yMax;        # in mm
	public $fontsize;    # in points
	public $spaceafter;  # true for last word in each line

	public function __construct($word, $xMin, $yMin, $xMax, $yMax,
			$fontsize, $spaceafter) {
		$this->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);