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 `&lt;',`&gt;', and `&amp;'.
248#
249def  html_quote( line ):
250    result = string.replace( line,   "&", "&amp;" )
251    result = string.replace( result, "<", "&lt;"  )
252    result = string.replace( result, ">", "&gt;"  )
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 = ( '&lsquo;<a href="' + url + '">'
341                            + block.title + '</a>&rsquo;'
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&lsquo;\2&rsquo;\3',
383                           line )
384            # convert tilde into non-breakable space
385            line = string.replace( line, "~", "&nbsp;" )
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 + "&nbsp;"
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                if block.source.filename.find( f ) >= 0:
680                    header = self.headers[f] + ' (' + f + ')'
681                    break
682
683#           if not header:
684#               sys.stderr.write(
685#                 "WARNING: No header macro for"
686#                 + " '" + block.source.filename + "'.\n" )
687
688            if header:
689                print ( header_location_header
690                        + 'Defined in ' + header + '.'
691                        + header_location_footer )
692
693            print source_header
694            for l in block.code:
695                print self.html_source_quote( l, block.name )
696            print source_footer
697
698    def  markup_enter( self, markup, block ):
699        if markup.tag == "description":
700            print description_header
701        else:
702            print marker_header + markup.tag + marker_inter
703
704        self.print_html_markup( markup )
705
706    def  markup_exit( self, markup, block ):
707        if markup.tag == "description":
708            print description_footer
709        else:
710            print marker_footer
711
712    def  block_exit( self, block ):
713        print( block_footer_start + self.file_prefix + "index.html"
714               + block_footer_middle + self.file_prefix + "toc.html"
715               + block_footer_end )
716
717    def  section_exit( self, section ):
718        print html_footer
719
720    def  section_dump_all( self ):
721        for section in self.sections:
722            self.section_dump( section,
723                               self.file_prefix + section.name + '.html' )
724
725# eof
726