= 2) { $n = 0x7F; // FIXME: This number should be DTE_BEGIN - 1 while(isset($used_chars[$n])) --$n; } else { $n = 1; while(isset($used_chars[$n])) ++$n; } } if($character == '«') $character = '"'; if($character == '»') $character = '"'; $charmap[$n] = $character; $used_chars[$n] = true; $unique_tiles[$tile] = $n; return $n; } function FontTile($index) { global $ascii_chars; if($index === '«') return "6C6C360000000000"; if($index === '»') return "6C6CD80000000000"; if(is_string($index)) $index = ord($index) - 0x20; $s = ''; for($y=0; $y<8; ++$y) $s .= @sprintf('%02X', $ascii_chars[$index*8+$y]); return $s; } function RenderedTile($render, $x) { $s = ''; for($y=0; $y<8; ++$y) $s .= @sprintf('%02X', $render[$x][$y]); return $s; } RegisterTile( FontTile(0x5F), chr(0xFF), 0xFF); if(USE_CHARMAP) { RegisterTile( "\n", "\n", 0x01 ); /* Just include some predefined characters that * we know we will use in the alphabet in certain * positions. In particular, '0' should be xxxx0000, * and it's nice if 'A' is xxxx1010. */ // 23456789ABCDEF0123456789ABCDEF012.. $s = " Israel.,d'oh!0123456789ABCDEF:)"; for($a=0; $a &$record) if(is_array($record)) { $s = $record['elem']; $b = strlen($s); $has_vwf = false; if(USE_VWF) foreach($vwf_strings as $stringdata) if(strpos($s, $stringdata[0]) !== false) { $has_vwf = true; break; } //if($round == 0 && $has_vwf) continue; //if(isset($record['treated'])) continue; for($a=0; $a < $b; ++$a) { $c = ord($s[$a]); if($c == 0x00 || $c == 0xFF) continue; if(USE_VWF) { foreach($vwf_strings as &$stringdata) { $string = $stringdata[0]; if(substr_compare($s, $string, $a, strlen($string)) == 0) { if($stringdata[1] == '') { $result = FontRender($string); DumpRenderedText($result, $result['lastx']); $replacement = ''; $x1=0; $x2=0; foreach($result as $x => $ylist) if(is_int($x)) { $x1=$x; break; } foreach($result as $x => $ylist) if(is_int($x)) { $x2=$x+1; } foreach($result as $x => $ylist) if(is_int($x)) { $tile = RenderedTile($result, $x); $px = (int)((0+$x-$x1) * strlen($string) / ($x2-$x1)); $px2= (int)((1+$x-$x1) * strlen($string) / ($x2-$x1)); $piece = substr($string, $px, $px2-$px); print "$x: $x1,$x2 -> $px,$px2 ($piece)\n"; $n = RegisterTile($tile, $piece, null); $replacement .= chr($n); } $stringdata[1] = $replacement; } $s = substr_replace($s, $stringdata[1], $a, strlen($string)); $a += strlen($stringdata[1]) - 1; $b = strlen($s); unset($stringdata); continue 2; } } unset($stringdata); } $n = RegisterTile( $s[$a]=="\n" ? "\n" : FontTile($s[$a]), $s[$a] ); $s[$a] = chr($n); } $record['elem'] = $s; $record['treated'] = true; } } unset($record); $n = 0xFF; while(!isset($used_chars[$n-1])) --$n; $begin = $n; $end = 0xFE; asort($charmap); $charset = str_pad('', 256, "\xFF"); $charset2 = str_pad('', 256, "\x00"); foreach($charmap as $new_chr => $orig_char) if( $new_chr >= $charset_offset) { $charset[ $new_chr-$charset_offset ] = $orig_char[0]; if(strlen($orig_char) > 1) $charset2[ $new_chr-$charset_offset ] = $orig_char[1]; } while($charset[ strlen($charset) - 1 ] == "\xFF") { $charset = substr($charset, 0, -1); $charset2 = substr($charset2, 0, -1); } $charset2_begin = 0; while($charset2[$charset2_begin] == "\x00") ++$charset2_begin; $s = Array(); foreach($charmap as $orig_char => $new_chr) $s[] = sprintf("%-3s -> %02X", make_string( chr($orig_char) ), ord($new_chr) ); foreach($s as $x => $s) { printf("%-14s", $s); if($x%5==4) print"\n"; } print "\n"; printf("begin=\$%02X, end=\$%02X, chars=%d\n", $begin, $end, strlen($charset)); } $fp = fopen($fontfn, 'wb'); foreach($unique_tiles as $tiledata => $index) { if(strlen($tiledata) == 16) { for($plane = 0; $plane < 2; ++$plane) { fseek($fp, (($index - $charset_offset + $plane*0x80) & 0xFF) * 16); $color = $plane ? 2 : 1; if($index == 0xFF) $color = 3; for($n=0; $n<2; ++$n) for($a=0; $a<8; ++$a) { $c = hexdec(substr($tiledata, $a*2,2)); $cc = $color; #if($a >= 6) $cc ^= 3; if(!($cc & (1 << $n))) $c = 0; fwrite($fp, chr($c)); } } } } fclose($fp); /* if(!USE_CHARMAP) { $begin = 0x7B; $end = 0xFE; $charset = ''; $charset_offset = 0x20; printf("begin=\$%02X, end=\$%02X\n", $begin, $end); } */ $size = count_size(); $best_size = $size + 4000; $best_dte = Array(); $best_records = $records; $first_records = $records; for($rounds = 0; $rounds < 8; ++$rounds) { $chosen_dte = Array(); $records = $first_records; $index = $end; while($index >= $begin) { $dte_pairs = Array(); foreach($records as $record_no => $record) if(is_array($record) && strpos($record['suffix'], 'DO_DTE') !== false) { $s = $record['elem']; $a = 0; $b = strlen($s); for($a=0; $a+1 < $b; ++$a) /* if($s[$a] != "\xFF" && $s[$a] != "\x00" # // These two rules are to avoid recursion: # && ord($s[$a]) < $begin # && (ord($s[$a+1]) < $begin || ord($s[$a+1] == "\xFF")) )*/ { @$dte_pairs[ 'X' . substr($s, $a, 2) ] += 1; } } arsort($dte_pairs); $m = rand(1,2); if($rounds < 1) $m = 1; for($n=$m; $n-->0; ) list($pair, $count) = each($dte_pairs); //printf("Chose pair: %s for %02X\n", make_string($pair), $index); $chosen_dte[$index] = $pair; $s = substr($pair, 1); $c = chr($index); foreach($records as $record_no => &$record) if(is_array($record) && strpos($record['suffix'], 'DO_DTE') !== false) $record['elem'] = str_replace( $s, $c, $record['elem']); unset($record); --$index; } /* Do this common-end-begin reduction only for statistics for now */ asort($chosen_dte); $tmp = $chosen_dte; $merged = 0; for(;;) { $suffixes = Array(); foreach($tmp as $index1 => $pair1) { if(!isset($suffixes[ 'X' . $pair1[2] ])) $suffixes[ 'X' . $pair1[2] ] = $index1; } $found = false; foreach($tmp as $index2 => $pair2) if(isset($suffixes[ 'X' . $pair2[1] ])) { $index1 = $suffixes[ 'X' . $pair2[1] ]; if($index1 != $index2) { ++$merged; unset($tmp[$index1]); unset($tmp[$index2]); $found = true; break; } } if(!$found) break; } $table_size = count($chosen_dte)*2 - $merged; $size2 = count_size() + strlen($charset)+1 + $table_size; if($size2 < $best_size) { $n = count($chosen_dte); print "Round $rounds, Got $size2 (table=$table_size for $n elements)...\n"; $best_size = $size2; $best_dte = $chosen_dte; $best_records = $records; } } print "Done, got $best_size\n"; $chosen_dte = $best_dte; $records = $best_records; /* Now, split the DTE table in two tables, one for first character and one for the second character, and sort the DTE table in such manner that the suffix of one table is the same as the prefix of the other table. */ asort($chosen_dte); $common_end = Array(); $common_begin = Array(); for(;;) { $suffixes = Array(); foreach($chosen_dte as $index1 => $pair1) { if(!isset($suffixes[ 'X' . $pair1[2] ])) $suffixes[ 'X' . $pair1[2] ] = $index1; } $found = false; foreach($chosen_dte as $index2 => $pair2) if(isset($suffixes[ 'X' . $pair2[1] ])) { $index1 = $suffixes[ 'X' . $pair2[1] ]; if($index1 != $index2) { $common_end[ $index1 ] = $chosen_dte[$index1]; $common_begin[ $index2 ] = $pair2; unset($chosen_dte[$index1]); unset($chosen_dte[$index2]); $found = true; break; } } if(!$found) break; } /* Renumber the DTE */ $translate_from = ''; $translate_to = ''; $renumbering = Array(); $new_dte = Array(); $index = $begin; foreach($common_end as $original_index => $pair) { $translate_from .= chr($original_index); $translate_to .= chr($index); $new_dte[$index++] = $pair; } foreach($chosen_dte as $original_index => $pair) { $translate_from .= chr($original_index); $translate_to .= chr($index); $new_dte[$index++] = $pair; } foreach($common_begin as $original_index => $pair) { $translate_from .= chr($original_index); $translate_to .= chr($index); $new_dte[$index++] = $pair; } foreach($records as $record_no => &$record) if(is_array($record)) $record['elem'] = strtr($record['elem'], $translate_from, $translate_to); unset($record); foreach($new_dte as $index => &$s) $s = strtr($s, $translate_from, $translate_to); unset($s); $merged = count($common_end); $size2 = count_size() + strlen($charset)+1; $table_size = count($new_dte)*2 - $merged; $size2 += $table_size; printf("From %d to %d, remain = %.2f %% (merged %d)\n", $size, $size2, $size2 * 100.0 / $size, $merged); ob_start(); printf("DTE_BEGIN=\$%02X\n". "DTE_END=\$%02X\n". "DTE_NEWLINE=%d\n". "DTE_CHARSET_OFFSET=%d\n". "DTE_CHARSET2_BEGIN=%d\n", $begin, $end, $newline, $charset_offset, $charset2_begin); printf(".segment \"RODATA\"\n"); print ".byte 10 ; Newline, needed for text_out\n"; if(strlen($charset)) printf("DTE_CHARSET: .byte %s\n", make_string($charset)); else printf("DTE_CHARSET: ; unused\n"); if(strlen($charset2) > $charset2_begin) printf("DTE_CHARSET2: .byte %s\n", make_string(substr($charset2, $charset2_begin))); else printf("DTE_CHARSET2: ; unused\n"); $s1 = ''; $s2 = ''; foreach($new_dte as $index => $pair) { $s1 .= $pair[1]; $s2 .= $pair[2]; printf("; .byte %-11s ;\$%02X\n", make_string(substr($pair,1)), $index); } if(strlen($s1) == 0) $s1 .= chr(0); if(strlen($s2) == 0) $s2 .= chr(0); printf("DTE_TABLE0: .byte %s ;part 1\n". "DTE_TABLE1: ", make_string(substr($s1, 0, strlen($s1)-$merged))); if($merged) printf( ".byte %s ;common part\n". " ", make_string(substr($s1, strlen($s1)-$merged))); printf(".byte %s ;part 2\n", make_string(substr($s2, $merged))); foreach($records as $record) if(is_array($record)) printf("%s %s %s\n", $record['prefix'], make_string($record['elem']), $record['suffix']); else print "{$record}\n"; $s = ob_get_clean(); file_put_contents($outputfn, $s); function FontRender($s) { global $ascii_chars, $character_margins, $kerning_table; $c = function($chr) { return ord($chr) - 0x20; }; $length = strlen($s); if($length == 0) return Array(); $extra = Array(); for(;;) { $result = Array(); $c2 = $c($s[0]); $x = -$character_margins[$c2][0]; $extra_candidates = Array(); $allow_right = -$x; for($a=0; $a<$length; ++$a) { if(isset($extra[$a])) { $x += $extra[$a]; } $b0 = (int)(($x+8)/8) - 1; $modulo = ($x+8)%8; $b1 = $b0+1; #print "{$s[$a]}: x=$x, b0=$b0, modulo=$modulo\n"; if($a > 0) { if($modulo == 7) $category = ($s[$a-1] == ' ') ? 1 : 2; else $category = ($s[$a] == ' ') ? 3 : 4; $m = @$extra[$a] - ($character_margins[$c1][0] /*+ $character_margins[$c1][1]*/); $extra_candidates[$a] = $category + $m; } else//if(false) { if($x < 0) { $category = 0; $m = @$extra[$a];// - ($character_margins[$c1][0] /*+ $character_margins[$c1][1]*/); $extra_candidates[$a] = $category + $m; } } $c1 = $c2; if($a+1 < $length) $c2 = $c($s[$a+1]); // Render character for($y=0; $y<8; ++$y) { $byte = $ascii_chars[$c1*8+$y]; if($b0 >= 0) @$result[$b0][$y] |= ($byte >> $modulo) & 0xFF; $v = ((($byte << 8) >> $modulo) & 0xFF); #if($v) #{ # if($b1 > $lastx) $lastx = $b1; @$result[$b1][$y] |= $v; #} // byte1 byte2 // 7654321076543210 // 76543210 -- modulo=7 // 76543210 -- modulo=6 // 76543210 -- modulo=4 // 76543210 -- modulo=4 // 76543210 -- modulo=3 // 76543210 -- modulo=2 // 76543210 -- modulo=1 // 76543210 -- modulo=0 } $x += $kerning_table[$c1][$c2]; } $allow_left = 7 - $character_margins[$c2][1]; if($c2 == 0x00) $allow_left = 7; foreach($result as $lastx => $dummy) { } for($lastx = $lastx*8+7; $lastx > 0; --$lastx) { $b = (int)($lastx / 8); $modulo = $lastx % 8; $bit = 0x80 >> $modulo; $empty = true; for($y=0; $y<8; ++$y) if($result[$b][$y]) { $empty = false; break; } if($empty) { unset($result[$b]); $lastx = $b * 8; } else { for($y=0; $y<8; ++$y) if($result[$b][$y] & $bit) break 2; } } //for($b=(int)(($lastx+7)/8); $b<90; ++$b) // unset($result[$b]); #DumpRenderedText($result, $lastx); /* If there is space after the last pixel, * add an empty pixel column somewhere. * Candidates are, in descending order of priority: * * 1. In the beginning of a character that immediately * follows a space where the modulo is 7 * 2. In the beginning of a character that has a modulo 7 * 3. In the beginning of a space character * 4. In the beginning of a random character except the first one */ if(7-($lastx % 8) <= $allow_left) break; asort($extra_candidates); list($a, $level) = each($extra_candidates); $tmp = Array(); do { $tmp[] = $a; list($a, $l) = each($extra_candidates); } while($l == $level); //$tmp = array_reverse($tmp); $a = $tmp[0]; //$a = $tmp[ array_rand($tmp) ]; @$extra[$a] += 1; } $result['lastx'] = $lastx; return $result; } function DumpRenderedText($result, $lastx = 99999) { for($y=0; $y<8; ++$y) { $first = null; foreach($result as $b=>$ytable) { if(!isset($first)) $first = $b; if($b === 'lastx') continue; $byte = @$ytable[$y]; $bg = ($b%2) ? '.' : ' '; if($b >= $first + 0x1D) $bg = '!'; for($x=0; $x<8; ++$x) { if($b*8+$x > $lastx) print '%'; else print ($byte & (0x80 >> $x)) ? '#' : $bg; } } print " -- ".join(',', array_keys($result))."\n"; } }