1# 2# tohtml.py 3# 4# A sub-class container of the `Formatter' class to produce HTML. 5# 6# Copyright 2002-2018 by 7# David Turner. 8# 9# This file is part of the FreeType project, and may only be used, 10# modified, and distributed under the terms of the FreeType project 11# license, LICENSE.TXT. By continuing to use, modify, or distribute 12# this file you indicate that you have read the license and 13# understand and accept it fully. 14 15# The parent class is contained in file `formatter.py'. 16 17 18from sources import * 19from content import * 20from formatter import * 21 22import time 23 24 25# The following strings define the HTML header used by all generated pages. 26html_header_1 = """\ 27<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 28"https://www.w3.org/TR/html4/loose.dtd"> 29<html> 30<head> 31<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 32<title>\ 33""" 34 35html_header_2 = """\ 36 API Reference</title> 37<style type="text/css"> 38 a:link { color: #0000EF; } 39 a:visited { color: #51188E; } 40 a:hover { color: #FF0000; } 41 42 body { font-family: Verdana, Geneva, Arial, Helvetica, serif; 43 color: #000000; 44 background: #FFFFFF; 45 width: 87%; 46 margin: auto; } 47 48 div.section { width: 75%; 49 margin: auto; } 50 div.section hr { margin: 4ex 0 1ex 0; } 51 div.section h4 { background-color: #EEEEFF; 52 font-size: medium; 53 font-style: oblique; 54 font-weight: bold; 55 margin: 3ex 0 1.5ex 9%; 56 padding: 0.3ex 0 0.3ex 1%; } 57 div.section p { margin: 1.5ex 0 1.5ex 10%; } 58 div.section pre { margin: 3ex 0 3ex 9%; 59 background-color: #D6E8FF; 60 padding: 2ex 0 2ex 1%; } 61 div.section table.fields { width: 90%; 62 margin: 1.5ex 0 1.5ex 10%; } 63 div.section table.toc { width: 95%; 64 margin: 1.5ex 0 1.5ex 5%; } 65 div.timestamp { text-align: center; 66 font-size: 69%; 67 margin: 1.5ex 0 1.5ex 0; } 68 69 h1 { text-align: center; } 70 h3 { font-size: medium; 71 margin: 4ex 0 1.5ex 0; } 72 73 p { text-align: justify; } 74 75 pre.colored { color: blue; } 76 77 span.keyword { font-family: monospace; 78 text-align: left; 79 white-space: pre; 80 color: darkblue; } 81 82 table.fields td.val { font-weight: bold; 83 text-align: right; 84 width: 30%; 85 vertical-align: baseline; 86 padding: 1ex 1em 1ex 0; } 87 table.fields td.desc { vertical-align: baseline; 88 padding: 1ex 0 1ex 1em; } 89 table.fields td.desc p:first-child { margin: 0; } 90 table.fields td.desc p { margin: 1.5ex 0 0 0; } 91 table.index { margin: 6ex auto 6ex auto; 92 border: 0; 93 border-collapse: separate; 94 border-spacing: 1em 0.3ex; } 95 table.index tr { padding: 0; } 96 table.index td { padding: 0; } 97 table.index-toc-link { width: 100%; 98 border: 0; 99 border-spacing: 0; 100 margin: 1ex 0 1ex 0; } 101 table.index-toc-link td.left { padding: 0 0.5em 0 0.5em; 102 font-size: 83%; 103 text-align: left; } 104 table.index-toc-link td.middle { padding: 0 0.5em 0 0.5em; 105 font-size: 83%; 106 text-align: center; } 107 table.index-toc-link td.right { padding: 0 0.5em 0 0.5em; 108 font-size: 83%; 109 text-align: right; } 110 table.synopsis { margin: 6ex auto 6ex auto; 111 border: 0; 112 border-collapse: separate; 113 border-spacing: 2em 0.6ex; } 114 table.synopsis tr { padding: 0; } 115 table.synopsis td { padding: 0; } 116 table.toc td.link { width: 30%; 117 text-align: right; 118 vertical-align: baseline; 119 padding: 1ex 1em 1ex 0; } 120 table.toc td.desc { vertical-align: baseline; 121 padding: 1ex 0 1ex 1em; 122 text-align: left; } 123 table.toc td.desc p:first-child { margin: 0; 124 text-align: left; } 125 table.toc td.desc p { margin: 1.5ex 0 0 0; 126 text-align: left; } 127 128</style> 129</head> 130<body> 131""" 132 133html_header_3l = """ 134<table class="index-toc-link"><tr><td class="left">[<a href="\ 135""" 136 137html_header_3r = """ 138<table class="index-toc-link"><tr><td class="right">[<a href="\ 139""" 140 141html_header_4 = """\ 142">Index</a>]</td><td class="right">[<a href="\ 143""" 144 145html_header_5t = """\ 146">TOC</a>]</td></tr></table> 147<h1>\ 148""" 149 150html_header_5i = """\ 151">Index</a>]</td></tr></table> 152<h1>\ 153""" 154 155html_header_6 = """\ 156 API Reference</h1> 157""" 158 159 160# The HTML footer used by all generated pages. 161html_footer = """\ 162</body> 163</html>\ 164""" 165 166# The header and footer used for each section. 167section_title_header1 = '<h1 id="' 168section_title_header2 = '">' 169section_title_footer = "</h1>" 170 171# The header and footer used for code segments. 172code_header = '<pre class="colored">' 173code_footer = '</pre>' 174 175# Paragraph header and footer. 176para_header = "<p>" 177para_footer = "</p>" 178 179# Block header and footer. 180block_header = '<div class="section">' 181block_footer_start = """\ 182<hr> 183<table class="index-toc-link"><tr><td class="left">[<a href="\ 184""" 185block_footer_middle = """\ 186">Index</a>]</td>\ 187<td class="middle">[<a href="#">Top</a>]</td>\ 188<td class="right">[<a href="\ 189""" 190block_footer_end = """\ 191">TOC</a>]</td></tr></table></div> 192""" 193 194# Description header/footer. 195description_header = "" 196description_footer = "" 197 198# Marker header/inter/footer combination. 199marker_header = "<h4>" 200marker_inter = "</h4>" 201marker_footer = "" 202 203# Header location header/footer. 204header_location_header = "<p>" 205header_location_footer = "</p>" 206 207# Source code extracts header/footer. 208source_header = "<pre>" 209source_footer = "</pre>" 210 211# Chapter header/inter/footer. 212chapter_header = """\ 213<div class="section"> 214<h2>\ 215""" 216chapter_inter = '</h2>' 217chapter_footer = '</div>' 218 219# Index footer. 220index_footer_start = """\ 221<hr> 222<table class="index-toc-link"><tr><td class="right">[<a href="\ 223""" 224index_footer_end = """\ 225">TOC</a>]</td></tr></table> 226""" 227 228# TOC footer. 229toc_footer_start = """\ 230<hr> 231<table class="index-toc-link"><tr><td class="left">[<a href="\ 232""" 233toc_footer_end = """\ 234">Index</a>]</td></tr></table> 235""" 236 237 238# Source language keyword coloration and styling. 239keyword_prefix = '<span class="keyword">' 240keyword_suffix = '</span>' 241 242section_synopsis_header = '<h2>Synopsis</h2>' 243section_synopsis_footer = '' 244 245 246# Translate a single line of source to HTML. This converts `<', `>', and 247# `&' into `<',`>', and `&'. 248# 249def html_quote( line ): 250 result = string.replace( line, "&", "&" ) 251 result = string.replace( result, "<", "<" ) 252 result = string.replace( result, ">", ">" ) 253 return result 254 255 256################################################################ 257## 258## HTML FORMATTER CLASS 259## 260class HtmlFormatter( Formatter ): 261 262 def __init__( self, processor, project_title, file_prefix ): 263 Formatter.__init__( self, processor ) 264 265 global html_header_1 266 global html_header_2 267 global html_header_3l, html_header_3r 268 global html_header_4 269 global html_header_5t, html_header_5i 270 global html_header_6 271 global html_footer 272 273 if file_prefix: 274 file_prefix = file_prefix + "-" 275 else: 276 file_prefix = "" 277 278 self.headers = processor.headers 279 self.project_title = project_title 280 self.file_prefix = file_prefix 281 self.html_header = ( 282 html_header_1 + project_title 283 + html_header_2 284 + html_header_3l + file_prefix + "index.html" 285 + html_header_4 + file_prefix + "toc.html" 286 + html_header_5t + project_title 287 + html_header_6 ) 288 self.html_index_header = ( 289 html_header_1 + project_title 290 + html_header_2 291 + html_header_3r + file_prefix + "toc.html" 292 + html_header_5t + project_title 293 + html_header_6 ) 294 self.html_toc_header = ( 295 html_header_1 + project_title 296 + html_header_2 297 + html_header_3l + file_prefix + "index.html" 298 + html_header_5i + project_title 299 + html_header_6 ) 300 self.html_footer = ( 301 '<div class="timestamp">generated on ' 302 + time.asctime( time.localtime( time.time() ) ) 303 + "</div>" + html_footer ) 304 305 self.columns = 3 306 307 def make_section_url( self, section ): 308 return self.file_prefix + section.name + ".html" 309 310 def make_block_url( self, block, name = None ): 311 if name == None: 312 name = block.name 313 314 try: 315 section_url = self.make_section_url( block.section ) 316 except: 317 # we already have a section 318 section_url = self.make_section_url( block ) 319 320 return section_url + "#" + name 321 322 def make_html_word( self, word ): 323 """Analyze a simple word to detect cross-references and markup.""" 324 # handle cross-references 325 m = re_crossref.match( word ) 326 if m: 327 try: 328 name = m.group( 'name' ) 329 rest = m.group( 'rest' ) 330 block = self.identifiers[name] 331 url = self.make_block_url( block ) 332 # display `foo[bar]' as `foo' 333 name = re.sub( r'\[.*\]', '', name ) 334 # normalize url, following RFC 3986 335 url = string.replace( url, "[", "(" ) 336 url = string.replace( url, "]", ")" ) 337 338 try: 339 # for sections, display title 340 url = ( '‘<a href="' + url + '">' 341 + block.title + '</a>’' 342 + rest ) 343 except: 344 url = ( '<a href="' + url + '">' 345 + name + '</a>' 346 + rest ) 347 348 return url 349 except: 350 # we detected a cross-reference to an unknown item 351 sys.stderr.write( "WARNING: undefined cross reference" 352 + " '" + name + "'.\n" ) 353 return '?' + name + '?' + rest 354 355 # handle markup for italic and bold 356 m = re_italic.match( word ) 357 if m: 358 name = m.group( 1 ) 359 rest = m.group( 2 ) 360 return '<i>' + name + '</i>' + rest 361 362 m = re_bold.match( word ) 363 if m: 364 name = m.group( 1 ) 365 rest = m.group( 2 ) 366 return '<b>' + name + '</b>' + rest 367 368 return html_quote( word ) 369 370 def make_html_para( self, words ): 371 """Convert words of a paragraph into tagged HTML text. Also handle 372 cross references.""" 373 line = "" 374 if words: 375 line = self.make_html_word( words[0] ) 376 for word in words[1:]: 377 line = line + " " + self.make_html_word( word ) 378 # handle hyperlinks 379 line = re_url.sub( r'<a href="\1">\1</a>', line ) 380 # convert `...' quotations into real left and right single quotes 381 line = re.sub( r"(^|\W)`(.*?)'(\W|$)", 382 r'\1‘\2’\3', 383 line ) 384 # convert tilde into non-breakable space 385 line = string.replace( line, "~", " " ) 386 387 return para_header + line + para_footer 388 389 def make_html_code( self, lines ): 390 """Convert a code sequence to HTML.""" 391 line = code_header + '\n' 392 for l in lines: 393 line = line + html_quote( l ).rstrip() + '\n' 394 395 return line + code_footer 396 397 def make_html_items( self, items ): 398 """Convert a field's content into HTML.""" 399 lines = [] 400 for item in items: 401 if item.lines: 402 lines.append( self.make_html_code( item.lines ) ) 403 else: 404 lines.append( self.make_html_para( item.words ) ) 405 406 return string.join( lines, '\n' ) 407 408 def print_html_items( self, items ): 409 print( self.make_html_items( items ) ) 410 411 def print_html_field( self, field ): 412 if field.name: 413 print( '<table><tr valign="top"><td><b>' 414 + field.name 415 + "</b></td><td>" ) 416 417 print( self.make_html_items( field.items ) ) 418 419 if field.name: 420 print( "</td></tr></table>" ) 421 422 def html_source_quote( self, line, block_name = None ): 423 result = "" 424 while line: 425 m = re_source_crossref.match( line ) 426 if m: 427 name = m.group( 2 ) 428 prefix = html_quote( m.group( 1 ) ) 429 length = len( m.group( 0 ) ) 430 431 if name == block_name: 432 # this is the current block name, if any 433 result = result + prefix + '<b>' + name + '</b>' 434 elif re_source_keywords.match( name ): 435 # this is a C keyword 436 result = ( result + prefix 437 + keyword_prefix + name + keyword_suffix ) 438 elif name in self.identifiers: 439 # this is a known identifier 440 block = self.identifiers[name] 441 id = block.name 442 443 # link to a field ID if possible 444 try: 445 for markup in block.markups: 446 if markup.tag == 'values': 447 for field in markup.fields: 448 if field.name: 449 id = name 450 451 result = ( result + prefix 452 + '<a href="' 453 + self.make_block_url( block, id ) 454 + '">' + name + '</a>' ) 455 except: 456 # sections don't have `markups'; however, we don't 457 # want references to sections here anyway 458 result = result + html_quote( line[:length] ) 459 460 else: 461 result = result + html_quote( line[:length] ) 462 463 line = line[length:] 464 else: 465 result = result + html_quote( line ) 466 line = [] 467 468 return result 469 470 def print_html_field_list( self, fields ): 471 print( '<table class="fields">' ) 472 for field in fields: 473 print( '<tr><td class="val" id="' + field.name + '">' 474 + field.name 475 + '</td><td class="desc">' ) 476 self.print_html_items( field.items ) 477 print( "</td></tr>" ) 478 print( "</table>" ) 479 480 def print_html_markup( self, markup ): 481 table_fields = [] 482 for field in markup.fields: 483 if field.name: 484 # We begin a new series of field or value definitions. We 485 # record them in the `table_fields' list before outputting 486 # all of them as a single table. 487 table_fields.append( field ) 488 else: 489 if table_fields: 490 self.print_html_field_list( table_fields ) 491 table_fields = [] 492 493 self.print_html_items( field.items ) 494 495 if table_fields: 496 self.print_html_field_list( table_fields ) 497 498 # 499 # formatting the index 500 # 501 def index_enter( self ): 502 print( self.html_index_header ) 503 self.index_items = {} 504 505 def index_name_enter( self, name ): 506 block = self.identifiers[name] 507 url = self.make_block_url( block ) 508 self.index_items[name] = url 509 510 def index_exit( self ): 511 # `block_index' already contains the sorted list of index names 512 count = len( self.block_index ) 513 rows = ( count + self.columns - 1 ) // self.columns 514 515 print( '<table class="index">' ) 516 for r in range( rows ): 517 line = "<tr>" 518 for c in range( self.columns ): 519 i = r + c * rows 520 if i < count: 521 bname = self.block_index[r + c * rows] 522 url = self.index_items[bname] 523 # display `foo[bar]' as `foo (bar)' 524 bname = string.replace( bname, "[", " (" ) 525 bname = string.replace( bname, "]", ")" ) 526 # normalize url, following RFC 3986 527 url = string.replace( url, "[", "(" ) 528 url = string.replace( url, "]", ")" ) 529 line = ( line + '<td><a href="' + url + '">' 530 + bname + '</a></td>' ) 531 else: 532 line = line + '<td></td>' 533 line = line + "</tr>" 534 print( line ) 535 536 print( "</table>" ) 537 538 print( index_footer_start 539 + self.file_prefix + "toc.html" 540 + index_footer_end ) 541 542 print( self.html_footer ) 543 544 self.index_items = {} 545 546 def index_dump( self, index_filename = None ): 547 if index_filename == None: 548 index_filename = self.file_prefix + "index.html" 549 550 Formatter.index_dump( self, index_filename ) 551 552 # 553 # formatting the table of contents 554 # 555 def toc_enter( self ): 556 print( self.html_toc_header ) 557 print( "<h1>Table of Contents</h1>" ) 558 559 def toc_chapter_enter( self, chapter ): 560 print( chapter_header + string.join( chapter.title ) + chapter_inter ) 561 print( '<table class="toc">' ) 562 563 def toc_section_enter( self, section ): 564 print( '<tr><td class="link">' 565 + '<a href="' + self.make_section_url( section ) + '">' 566 + section.title + '</a></td><td class="desc">' ) 567 print( self.make_html_para( section.abstract ) ) 568 569 def toc_section_exit( self, section ): 570 print( "</td></tr>" ) 571 572 def toc_chapter_exit( self, chapter ): 573 print( "</table>" ) 574 print( chapter_footer ) 575 576 def toc_index( self, index_filename ): 577 print( chapter_header 578 + '<a href="' + index_filename + '">Global Index</a>' 579 + chapter_inter + chapter_footer ) 580 581 def toc_exit( self ): 582 print( toc_footer_start 583 + self.file_prefix + "index.html" 584 + toc_footer_end ) 585 586 print( self.html_footer ) 587 588 def toc_dump( self, toc_filename = None, index_filename = None ): 589 if toc_filename == None: 590 toc_filename = self.file_prefix + "toc.html" 591 592 if index_filename == None: 593 index_filename = self.file_prefix + "index.html" 594 595 Formatter.toc_dump( self, toc_filename, index_filename ) 596 597 # 598 # formatting sections 599 # 600 def section_enter( self, section ): 601 print( self.html_header ) 602 603 print( section_title_header1 + section.name + section_title_header2 604 + section.title 605 + section_title_footer ) 606 607 maxwidth = 0 608 for b in section.blocks.values(): 609 if len( b.name ) > maxwidth: 610 maxwidth = len( b.name ) 611 612 width = 70 # XXX magic number 613 if maxwidth > 0: 614 # print section synopsis 615 print( section_synopsis_header ) 616 print( '<table class="synopsis">' ) 617 618 columns = width // maxwidth 619 if columns < 1: 620 columns = 1 621 622 count = len( section.block_names ) 623 # don't handle last entry if it is empty 624 if section.block_names[-1] == "/empty/": 625 count -= 1 626 rows = ( count + columns - 1 ) // columns 627 628 for r in range( rows ): 629 line = "<tr>" 630 for c in range( columns ): 631 i = r + c * rows 632 line = line + '<td>' 633 if i < count: 634 name = section.block_names[i] 635 if name == "/empty/": 636 # it can happen that a complete row is empty, and 637 # without a proper `filler' the browser might 638 # collapse the row to a much smaller height (or 639 # even omit it completely) 640 line = line + " " 641 else: 642 url = name 643 # display `foo[bar]' as `foo' 644 name = re.sub( r'\[.*\]', '', name ) 645 # normalize url, following RFC 3986 646 url = string.replace( url, "[", "(" ) 647 url = string.replace( url, "]", ")" ) 648 line = ( line + '<a href="#' + url + '">' 649 + name + '</a>' ) 650 651 line = line + '</td>' 652 line = line + "</tr>" 653 print( line ) 654 655 print( "</table>" ) 656 print( section_synopsis_footer ) 657 658 print( description_header ) 659 print( self.make_html_items( section.description ) ) 660 print( description_footer ) 661 662 def block_enter( self, block ): 663 print( block_header ) 664 665 # place html anchor if needed 666 if block.name: 667 url = block.name 668 # display `foo[bar]' as `foo' 669 name = re.sub( r'\[.*\]', '', block.name ) 670 # normalize url, following RFC 3986 671 url = string.replace( url, "[", "(" ) 672 url = string.replace( url, "]", ")" ) 673 print( '<h3 id="' + url + '">' + name + '</h3>' ) 674 675 # dump the block C source lines now 676 if block.code: 677 header = '' 678 for f in self.headers.keys(): 679 header_filename = os.path.normpath(block.source.filename) 680 if header_filename.find( os.path.normpath( f ) ) >= 0: 681 header = self.headers[f] + ' (' + f + ')' 682 break 683 684# if not header: 685# sys.stderr.write( 686# "WARNING: No header macro for" 687# + " '" + block.source.filename + "'.\n" ) 688 689 if header: 690 print( header_location_header 691 + 'Defined in ' + header + '.' 692 + header_location_footer ) 693 694 print( source_header ) 695 for l in block.code: 696 print( self.html_source_quote( l, block.name ) ) 697 print( source_footer ) 698 699 def markup_enter( self, markup, block ): 700 if markup.tag == "description": 701 print( description_header ) 702 else: 703 print( marker_header + markup.tag + marker_inter ) 704 705 self.print_html_markup( markup ) 706 707 def markup_exit( self, markup, block ): 708 if markup.tag == "description": 709 print( description_footer ) 710 else: 711 print( marker_footer ) 712 713 def block_exit( self, block ): 714 print( block_footer_start + self.file_prefix + "index.html" 715 + block_footer_middle + self.file_prefix + "toc.html" 716 + block_footer_end ) 717 718 def section_exit( self, section ): 719 print( html_footer ) 720 721 def section_dump_all( self ): 722 for section in self.sections: 723 self.section_dump( section, 724 self.file_prefix + section.name + '.html' ) 725 726# eof 727