1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package build.tools.jigsaw; 27 28 import java.io.IOException; 29 import java.io.PrintStream; 30 import java.lang.module.Configuration; 31 import java.lang.module.ModuleDescriptor; 32 import java.lang.module.ModuleFinder; 33 import java.lang.module.ModuleReference; 34 import java.lang.module.ResolvedModule; 35 import java.nio.file.Files; 36 import java.nio.file.Path; 37 import java.nio.file.Paths; 38 import java.util.Arrays; 39 import java.util.Comparator; 40 import java.util.Date; 41 import java.util.Enumeration; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.Map; 45 import java.util.Set; 46 import java.util.stream.Collectors; 47 import java.util.zip.ZipEntry; 48 import java.util.zip.ZipFile; 49 import static java.lang.module.ModuleDescriptor.*; 50 import static build.tools.jigsaw.ModuleSummary.HtmlDocument.Selector.*; 51 import static build.tools.jigsaw.ModuleSummary.HtmlDocument.Division.*; 52 53 public class ModuleSummary { 54 private static final String USAGE = "Usage: ModuleSummary --module-path <dir> -o <outfile> [--root mn]*"; 55 56 public static void main(String[] args) throws Exception { 57 int i=0; 58 Path modpath = null; 59 Path outfile = null; 60 Set<String> roots = new HashSet<>(); 61 while (i < args.length && args[i].startsWith("-")) { 62 String arg = args[i++]; 63 switch (arg) { 64 case "--module-path": 65 modpath = Paths.get(args[i++]); 66 break; 67 case "-o": 68 outfile = Paths.get(args[i++]); 69 break; 70 case "--root": 71 roots.add(args[i++]); 72 default: 73 System.err.println(USAGE); 74 System.exit(-1); 75 } 76 } 77 if (outfile == null || modpath == null) { 78 System.err.println(USAGE); 79 System.exit(1); 80 } 81 Path dir = outfile.getParent() != null ? outfile.getParent() : Paths.get("."); 82 Files.createDirectories(dir); 83 84 Map<String, ModuleSummary> modules = new HashMap<>(); 85 Set<ModuleReference> mrefs = ModuleFinder.ofSystem().findAll(); 86 for (ModuleReference mref : mrefs) { 87 String mn = mref.descriptor().name(); 88 Path jmod = modpath.resolve(mn + ".jmod"); 89 modules.put(mn, new ModuleSummary(mref, jmod)); 90 } 91 92 if (roots.isEmpty()) { 93 roots.addAll(modules.keySet()); 94 } 95 genReport(outfile, modules, roots, "JDK Module Summary"); 96 } 97 98 static void genReport(Path outfile, Map<String, ModuleSummary> modules, Set<String> roots, String title) 99 throws IOException 100 { 101 Configuration cf = resolve(roots); 102 try (PrintStream out = new PrintStream(Files.newOutputStream(outfile))) { 103 HtmlDocument doc = new HtmlDocument(title, modules); 104 Set<ModuleDescriptor> descriptors = cf.modules().stream() 105 .map(ResolvedModule::reference) 106 .map(ModuleReference::descriptor) 107 .collect(Collectors.toSet()); 108 doc.writeTo(out, descriptors); 109 } 110 } 111 112 private final String name; 113 private final ModuleDescriptor descriptor; 114 private final JmodInfo jmodInfo; 115 ModuleSummary(ModuleReference mref, Path jmod) throws IOException { 116 this.name = mref.descriptor().name(); 117 this.descriptor = mref.descriptor(); 118 this.jmodInfo = new JmodInfo(jmod); 119 } 120 121 String name() { 122 return name; 123 } 124 125 long uncompressedSize() { 126 return jmodInfo.size; 127 } 128 129 long jmodFileSize() { 130 return jmodInfo.filesize; // estimated compressed size 131 } 132 133 ModuleDescriptor descriptor() { 134 return descriptor; 135 } 136 137 int numClasses() { 138 return jmodInfo.classCount; 139 } 140 141 long classBytes() { 142 return jmodInfo.classBytes; 143 } 144 145 int numResources() { 146 return jmodInfo.resourceCount; 147 } 148 149 long resourceBytes() { 150 return jmodInfo.resourceBytes; 151 } 152 153 int numConfigs() { 154 return jmodInfo.configCount; 155 } 156 long configBytes() { 157 return jmodInfo.configBytes; 158 } 159 int numCommands() { 160 return jmodInfo.nativeCmds.size(); 161 } 162 163 long commandBytes() { 164 return jmodInfo.nativeCmds.values().stream() 165 .mapToLong(l -> l.longValue()).sum() - jmodInfo.debugInfoCmdBytes; 166 } 167 int numCommandsDebug() { 168 return jmodInfo.debugInfoCmdCount; 169 } 170 long commandDebugBytes() { 171 return jmodInfo.debugInfoCmdBytes; 172 } 173 int numNativeLibraries() { 174 return jmodInfo.nativeLibs.size(); 175 } 176 177 long nativeLibrariesBytes() { 178 return jmodInfo.nativeLibs.values().stream() 179 .mapToLong(l -> l.longValue()).sum() - jmodInfo.debugInfoLibBytes; 180 } 181 int numNativeLibrariesDebug() { 182 return jmodInfo.debugInfoLibCount; 183 } 184 185 long nativeLibrariesDebugBytes() { 186 return jmodInfo.debugInfoLibBytes; 187 } 188 189 Map<String,Long> commands() { 190 return jmodInfo.nativeCmds; 191 } 192 193 Map<String,Long> nativeLibs() { 194 return jmodInfo.nativeLibs; 195 } 196 197 Map<String,Long> configFiles() { 198 return jmodInfo.configFiles; 199 } 200 201 202 static class JmodInfo { 203 final long size; 204 final long filesize; 205 final int classCount; 206 final long classBytes; 207 final int resourceCount; 208 final long resourceBytes; 209 final int configCount; 210 final long configBytes; 211 final int debugInfoLibCount; 212 final long debugInfoLibBytes; 213 final int debugInfoCmdCount; 214 final long debugInfoCmdBytes; 215 final Map<String,Long> configFiles = new HashMap<>(); 216 final Map<String,Long> nativeCmds = new HashMap<>(); 217 final Map<String,Long> nativeLibs = new HashMap<>(); 218 219 JmodInfo(Path jmod) throws IOException { 220 long total = 0; 221 long cBytes = 0, rBytes = 0, cfBytes = 0, dizLibBytes = 0, dizCmdBytes = 0; 222 int cCount = 0, rCount = 0, cfCount = 0, dizLibCount = 0, dizCmdCount = 0; 223 try (ZipFile zf = new ZipFile(jmod.toFile())) { 224 for (Enumeration<? extends ZipEntry> e = zf.entries(); e.hasMoreElements(); ) { 225 ZipEntry ze = e.nextElement(); 226 String fn = ze.getName(); 227 int pos = fn.indexOf('/'); 228 String dir = fn.substring(0, pos); 229 String filename = fn.substring(fn.lastIndexOf('/') + 1); 230 // name shown in the column 231 String name = filename; 232 233 long len = ze.getSize(); 234 total += len; 235 switch (dir) { 236 case NATIVE_LIBS: 237 nativeLibs.put(name, len); 238 if (filename.endsWith(".diz")) { 239 dizLibCount++; 240 dizLibBytes += len; 241 } 242 break; 243 case NATIVE_CMDS: 244 nativeCmds.put(name, len); 245 if (filename.endsWith(".diz")) { 246 dizCmdCount++; 247 dizCmdBytes += len; 248 } 249 break; 250 case CLASSES: 251 if (filename.endsWith(".class")) { 252 cCount++; 253 cBytes += len; 254 } else { 255 rCount++; 256 rBytes += len; 257 } 258 break; 259 case CONFIG: 260 configFiles.put(name, len); 261 cfCount++; 262 cfBytes += len; 263 break; 264 default: 265 break; 266 } 267 } 268 this.filesize = jmod.toFile().length(); 269 this.classCount = cCount; 270 this.classBytes = cBytes; 271 this.resourceCount = rCount; 272 this.resourceBytes = rBytes; 273 this.configCount = cfCount; 274 this.configBytes = cfBytes; 275 this.size = total; 276 this.debugInfoLibCount = dizLibCount; 277 this.debugInfoLibBytes = dizLibBytes; 278 this.debugInfoCmdCount = dizCmdCount; 279 this.debugInfoCmdBytes = dizCmdBytes; 280 } 281 } 282 283 static final String NATIVE_LIBS = "native"; 284 static final String NATIVE_CMDS = "bin"; 285 static final String CLASSES = "classes"; 286 static final String CONFIG = "conf"; 287 288 static final String MODULE_ID = "module/id"; 289 static final String MODULE_MAIN_CLASS = "module/main-class"; 290 } 291 292 static Configuration resolve(Set<String> roots) { 293 return Configuration.empty() 294 .resolveRequires(ModuleFinder.ofSystem(), 295 ModuleFinder.of(), 296 roots); 297 } 298 299 static class HtmlDocument { 300 final String title; 301 final Map<String, ModuleSummary> modules; 302 boolean requiresTransitiveNote = false; 303 boolean aggregatorNote = false; 304 boolean totalBytesNote = false; 305 HtmlDocument(String title, Map<String, ModuleSummary> modules) { 306 this.title = title; 307 this.modules = modules; 308 } 309 310 void writeTo(PrintStream out, Set<ModuleDescriptor> selectedModules) { 311 out.format("<html><head>%n"); 312 out.format("<title>%s</title>%n", title); 313 // stylesheet 314 Arrays.stream(HtmlDocument.STYLES).forEach(out::println); 315 out.format("</head>%n"); 316 317 // body begins 318 out.format("<body>%n"); 319 320 // title and date 321 out.println(DOCTITLE.toString(title)); 322 out.println(VERSION.toString(String.format("%tc", new Date()))); 323 324 // total modules and sizes 325 long totalBytes = selectedModules.stream() 326 .map(ModuleDescriptor::name) 327 .map(modules::get) 328 .mapToLong(ModuleSummary::uncompressedSize) 329 .sum(); 330 String[] sections = new String[] { 331 String.format("%s: %d", "Total modules", selectedModules.size()), 332 String.format("%s: %,d bytes (%s %s)", "Total size", 333 totalBytes, 334 System.getProperty("os.name"), 335 System.getProperty("os.arch")) 336 }; 337 out.println(SECTION.toString(sections)); 338 339 // write table and header 340 out.println(String.format("<table class=\"%s\">", MODULES)); 341 out.println(header("Module", "Requires", "Exports", 342 "Services", "Commands/Native Libraries/Configs")); 343 344 // write contents - one row per module 345 selectedModules.stream() 346 .sorted(Comparator.comparing(ModuleDescriptor::name)) 347 .map(m -> modules.get(m.name())) 348 .map(ModuleTableRow::new) 349 .forEach(table -> table.writeTo(out)); 350 351 out.format("</table>"); // end table 352 out.format("</body>"); 353 out.println("</html>"); 354 } 355 356 String header(String... columns) { 357 StringBuilder sb = new StringBuilder(); 358 sb.append("<tr>"); 359 Arrays.stream(columns) 360 .forEach(cn -> sb.append(" <th>").append(cn).append("</th>").append("\n")); 361 sb.append("</tr>"); 362 return sb.toString(); 363 } 364 365 static enum Selector { 366 MODULES("modules"), 367 MODULE("module"), 368 MODULE_DEF("code name def"), 369 AGGREGATOR("code name def agg"), 370 REQUIRES("code"), 371 REQUIRES_PUBLIC("code reexp"), 372 BR("br"), 373 CODE("code"), 374 NUMBER("number"),; 375 final String name; 376 Selector(String name) { 377 this.name = name; 378 } 379 @Override 380 public String toString() { 381 return name; 382 } 383 } 384 385 static enum Division { 386 DOCTITLE("doctitle"), 387 VERSION("versions"), 388 SECTION("section"); 389 final String name; 390 391 Division(String name) { 392 this.name = name; 393 } 394 395 public String toString(String... lines) { 396 String value = Arrays.stream(lines).collect(Collectors.joining("<br>\n")); 397 return "<div class=\"" + name + "\">" + value + "</div>"; 398 } 399 } 400 401 class ModuleTableRow { 402 private final ModuleSummary ms; 403 private final Set<ModuleDescriptor> deps; 404 private final int maxRows; 405 private final boolean aggregator; 406 ModuleTableRow(ModuleSummary ms) { 407 this.ms = ms; 408 Configuration cf = resolve(Set.of(ms.name())); 409 this.deps = cf.modules().stream() 410 .map(ResolvedModule::reference) 411 .map(ModuleReference::descriptor) 412 .collect(Collectors.toSet()); 413 int count = (ms.numClasses() > 0 ? 1 : 0) + 414 (ms.numResources() > 0 ? 1 : 0) + 415 (ms.numConfigs() > 0 ? 1 : 0) + 416 (ms.numNativeLibraries() > 0 ? 1 : 0) + 417 (ms.numNativeLibrariesDebug() > 0 ? 1 : 0) + 418 (ms.numCommands() > 0 ? 1 : 0) + 419 (ms.numCommandsDebug() > 0 ? 1 : 0); 420 this.aggregator = ms.numClasses() == 1 && count == 1; // only module-info.class 421 422 // 5 fixed rows (name + 2 transitive count/size + 2 blank rows) 423 this.maxRows = 5 + count + (aggregator && !aggregatorNote ? 2 : 0); 424 } 425 426 public void writeTo(PrintStream out) { 427 out.println(String.format("<tr id=\"%s\" class=\"%s\">", ms.name(), MODULE)); 428 out.println(moduleColumn()); 429 out.println(requiresColumn()); 430 out.println(exportsColumn()); 431 out.println(servicesColumn()); 432 out.println(otherSectionColumn()); 433 out.println("</td>"); 434 out.println("</tr>"); 435 } 436 437 public String moduleColumn() { 438 // module name 439 StringBuilder sb = new StringBuilder(" "); 440 sb.append("<td>"); 441 sb.append(String.format("<table class=\"%s\">", MODULE)).append("\n"); 442 sb.append(moduleName(ms.name())); 443 sb.append(blankRow()); 444 // metadata 445 sb.append(toTableRow("class", "classes", ms.numClasses(), ms.classBytes())); 446 sb.append(toTableRow("resource", "resources", ms.numResources(), ms.resourceBytes())); 447 sb.append(toTableRow("config", "configs", ms.numConfigs(), ms.configBytes())); 448 sb.append(toTableRow("native library", "native libraries", 449 ms.numNativeLibraries(), ms.nativeLibrariesBytes())); 450 sb.append(toTableRow("native library debug", "native libraries debug", 451 ms.numNativeLibrariesDebug(), ms.nativeLibrariesDebugBytes())); 452 sb.append(toTableRow("command", "commands", ms.numCommands(), ms.commandBytes())); 453 sb.append(toTableRow("command debug", "commands debug", 454 ms.numCommandsDebug(), ms.commandDebugBytes())); 455 sb.append(blankRow()); 456 457 // transitive dependencies 458 long reqBytes = deps.stream() 459 .filter(d -> !d.name().equals(ms.name())) 460 .mapToLong(d -> modules.get(d.name()).uncompressedSize()) 461 .sum(); 462 long reqJmodFileSize = deps.stream() 463 .mapToLong(d -> modules.get(d.name()).jmodFileSize()) 464 .sum(); 465 // size 466 if (totalBytesNote) { 467 sb.append(toTableRow("Total bytes", ms.uncompressedSize())); 468 sb.append(toTableRow("Total bytes of dependencies", reqBytes)); 469 } else { 470 // print footnote 471 sb.append(toTableRow("Total bytes<sup>1</sup>", ms.uncompressedSize())); 472 sb.append(toTableRow("Total bytes of dependencies<sup>2</sup>", reqBytes)); 473 } 474 String files = deps.size() == 1 ? "file" : "files"; 475 sb.append(toTableRow(String.format("Total jmod bytes (%d %s)", deps.size(), files), reqJmodFileSize)); 476 477 if (aggregator && !aggregatorNote) { 478 aggregatorNote = true; 479 sb.append(blankRow()); 480 sb.append(toTableRow("<i>* aggregator is a module with module-info.class only</i>", BR)); 481 } 482 if (!totalBytesNote) { 483 totalBytesNote = true; 484 sb.append(blankRow()); 485 sb.append(toTableRow("<i><sup>1</sup>sum of all files including debug files</i>", BR)); 486 sb.append(toTableRow("<i><sup>2</sup>sum of direct and indirect dependencies</i>", BR)); 487 } 488 sb.append("</table>").append("</td>"); 489 return sb.toString(); 490 } 491 492 private String moduleName(String mn) { 493 if (aggregator) { 494 StringBuilder sb = new StringBuilder(); 495 sb.append(String.format("<tr><td colspan=\"2\"><span class=\"%s\">", AGGREGATOR)) 496 .append(mn) 497 .append("</span>").append(" "); 498 if (!aggregatorNote) { 499 sb.append("(aggregator<sup>*</sup>)"); 500 } else { 501 sb.append("(aggregator)"); 502 } 503 sb.append("</td></tr>"); 504 return sb.toString(); 505 } else { 506 return toTableRow(mn, MODULE_DEF); 507 } 508 } 509 510 public String requiresColumn() { 511 StringBuilder sb = new StringBuilder(); 512 sb.append(String.format("<td>")); 513 boolean footnote = requiresTransitiveNote; 514 ms.descriptor().requires().stream() 515 .sorted(Comparator.comparing(Requires::name)) 516 .forEach(r -> { 517 boolean requiresTransitive = r.modifiers().contains(Requires.Modifier.TRANSITIVE); 518 Selector sel = requiresTransitive ? REQUIRES_PUBLIC : REQUIRES; 519 String req = String.format("<a class=\"%s\" href=\"#%s\">%s</a>", 520 sel, r.name(), r.name()); 521 if (!requiresTransitiveNote && requiresTransitive) { 522 requiresTransitiveNote = true; 523 req += "<sup>*</sup>"; 524 } 525 sb.append(req).append("\n").append("<br>"); 526 }); 527 528 if (!ms.name().equals("java.base")) { 529 int directDeps = ms.descriptor().requires().size(); 530 int indirectDeps = deps.size()-directDeps-1; 531 for (int i=directDeps; i< (maxRows-1); i++) { 532 sb.append("<br>"); 533 } 534 sb.append("<br>"); 535 sb.append("<i>+").append(indirectDeps).append(" transitive dependencies</i>"); 536 } 537 if (footnote != requiresTransitiveNote) { 538 sb.append("<br><br>").append("<i>* bold denotes requires transitive</i>"); 539 } 540 sb.append("</td>"); 541 return sb.toString(); 542 } 543 544 public String exportsColumn() { 545 StringBuilder sb = new StringBuilder(); 546 sb.append(String.format(" <td class=\"%s\">", CODE)); 547 ms.descriptor().exports().stream() 548 .sorted(Comparator.comparing(Exports::source)) 549 .filter(e -> !e.isQualified()) 550 .forEach(e -> sb.append(e.source()).append("<br>").append("\n")); 551 sb.append("</td>"); 552 return sb.toString(); 553 } 554 555 public String servicesColumn() { 556 StringBuilder sb = new StringBuilder(); 557 sb.append(String.format(" <td class=\"%s\">", CODE)); 558 ms.descriptor().uses().stream() 559 .sorted() 560 .forEach(s -> sb.append("uses ").append(s).append("<br>").append("\n")); 561 ms.descriptor().provides().stream() 562 .sorted(Comparator.comparing(Provides::service)) 563 .map(p -> String.format("provides %s<br> with %s", 564 p.service(), p.providers())) 565 .forEach(p -> sb.append(p).append("<br>").append("\n")); 566 sb.append("</td>"); 567 return sb.toString(); 568 } 569 570 public String otherSectionColumn() { 571 StringBuilder sb = new StringBuilder(); 572 sb.append("<td>"); 573 sb.append(String.format("<table class=\"%s\">", MODULE)).append("\n"); 574 // commands 575 if (ms.numCommands() > 0) { 576 sb.append(toTableRow("bin/", CODE)); 577 ms.commands().entrySet().stream() 578 .sorted(Map.Entry.comparingByKey()) 579 .forEach(e -> sb.append(toTableRow(e.getKey(), e.getValue(), CODE))); 580 sb.append(blankRow()); 581 } 582 583 // native libraries 584 if (ms.numNativeLibraries() > 0) { 585 sb.append(toTableRow("lib/", CODE)); 586 ms.nativeLibs().entrySet().stream() 587 .sorted(Map.Entry.comparingByKey()) 588 .forEach(e -> sb.append(toTableRow(e.getKey(), e.getValue(), CODE))); 589 sb.append(blankRow()); 590 } 591 592 // config files 593 if (ms.numConfigs() > 0) { 594 sb.append(toTableRow("conf/", CODE)); 595 ms.configFiles().entrySet().stream() 596 .sorted(Map.Entry.comparingByKey()) 597 .forEach(e -> sb.append(toTableRow(e.getKey(), e.getValue(), CODE))); 598 } 599 // totals 600 sb.append("</table>").append("</td>"); 601 return sb.toString(); 602 } 603 604 private String blankRow() { 605 return toTableRow(" ", BR); 606 } 607 608 private String toTableRow(String col, Selector selector) { 609 TableDataBuilder builder = new TableDataBuilder(); 610 builder.colspan(selector, 2, col); 611 return builder.build(); 612 } 613 614 private String toTableRow(String col1, long col2) { 615 return toTableRow(col1, col2, BR); 616 } 617 618 private String toTableRow(String col1, long col2, Selector selector) { 619 TableDataBuilder builder = new TableDataBuilder(); 620 builder.data(selector, col1); 621 builder.data(col2); 622 return builder.build(); 623 624 } 625 626 private String toTableRow(String singular, String plural, int count, long bytes) { 627 if (count == 0) { 628 return ""; 629 } 630 TableDataBuilder builder = new TableDataBuilder(); 631 if (count == 1) { 632 builder.data(count + " " + singular); 633 } else { 634 builder.data(count + " " + plural); 635 } 636 builder.data(bytes); 637 return builder.build(); 638 } 639 640 class TableDataBuilder { 641 private final StringBuilder sb; 642 TableDataBuilder() { 643 this.sb = new StringBuilder("<tr>"); 644 } 645 TableDataBuilder data(String s) { 646 data(BR, s); 647 return this; 648 } 649 TableDataBuilder data(long num) { 650 data(NUMBER, String.format("%,d", num)); 651 return this; 652 } 653 TableDataBuilder colspan(Selector selector, int columns, String data) { 654 sb.append("<td colspan=\"").append(columns).append("\">"); 655 sb.append("<span class=\"").append(selector).append("\">"); 656 sb.append(data).append("</span></td>"); 657 return this; 658 } 659 660 TableDataBuilder data(Selector selector, String data) { 661 sb.append("<td class=\"").append(selector).append("\">"); 662 sb.append(data).append("</td>"); 663 return this; 664 } 665 String build() { 666 sb.append("</tr>"); 667 return sb.toString(); 668 } 669 } 670 } 671 672 private static final String[] STYLES = new String[]{ 673 "<link rel=\"stylesheet\" type=\"text/css\" href=\"/.fonts/dejavu.css\"/>", 674 "<style type=\"text/css\">", 675 " HTML, BODY, DIV, SPAN, APPLET, OBJECT, IFRAME, H1, H2, H3, H4, H5, H6, P,", 676 " BLOCKQUOTE, PRE, A, ABBR, ACRONYM, ADDRESS, BIG, CITE, CODE, DEL, DFN, EM,", 677 " IMG, INS, KBD, Q, S, SAMP, SMALL, STRIKE, STRONG, SUB, SUP, TT, VAR, B, U,", 678 " I, CENTER, DL, DT, DD, OL, UL, LI, FIELDSET, FORM, LABEL, LEGEND, TABLE,", 679 " CAPTION, TBODY, TFOOT, THEAD, TR, TH, TD, ARTICLE, ASIDE, CANVAS, DETAILS,", 680 " EMBED, FIGURE, FIGCAPTION, FOOTER, HEADER, HGROUP, MENU, NAV, OUTPUT, RUBY,", 681 " SECTION, SUMMARY, TIME, MARK, AUDIO, VIDEO {", 682 " margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit;", 683 " vertical-align: baseline; }", 684 " ARTICLE, ASIDE, DETAILS, FIGCAPTION, FIGURE, ", 685 " FOOTER, HEADER, HGROUP, MENU, NAV, SECTION { display: block; }", 686 " BLOCKQUOTE, Q { quotes: none; }", 687 " BLOCKQUOTE:before, BLOCKQUOTE:after, Q:before, Q:after {", 688 " content: ''; content: none; }", 689 " TABLE { border-collapse: collapse; border-spacing: 0; }", 690 " A { text-decoration: none; }", 691 " A:link { color: #437291; }", 692 " A:visited { color: #666666; }", 693 " A.anchor:link, A.anchor:visited { color: black; }", 694 " A[href]:hover { color: #e76f00; }", 695 " A IMG { border-width: 0px; }", 696 " HTML { font-size: 20px; } /* baseline grid */", 697 " HTML > BODY { font-size: 14px; }", 698 " BODY {", 699 " background: white;", 700 " margin: 40px;", 701 " margin-bottom: 150%;", 702 " line-height: 20px;", 703 " -webkit-text-size-adjust: 100%; /* iOS */", 704 " color: #222;", 705 " }", 706 " BODY { font-family: \"DejaVu Serif\", \"Lucida Bright\", \"Bookman Old Style\",", 707 " Georgia, serif; }", 708 " CODE, TT, .jref, DIV.spec .open, TABLE.profiles {", 709 " font-family: \"DejaVu Sans\", \"Lucida Sans\", Helvetica, sans-serif; }", 710 " PRE, .code { font-family: \"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\",", 711 " Monaco, \"Courier New\", monospace; }", 712 " H1, H2, H3, H4 { color: green; font-weight: bold; }", 713 " I { font-style: italic; }", 714 " TH { font-weight: bold; }", 715 " P { text-indent: 40px; }", 716 " P:first-child, UL + P, OL + P, BLOCKQUOTE + P, TABLE + P, P.subsection,", 717 " P.break, DIV.profiles-table + P { text-indent: 0; }", 718 " P.break { margin-top: 10px; }", 719 " P.subsection { margin-top: 20px; }", 720 " P.subsection SPAN.title { font-weight: bold; padding-right: 20px; }", 721 " UL, OL { margin: 10px 0; padding-left: 40px; }", 722 " LI { margin-bottom: 10px; }", 723 " UL.compact LI { margin-bottom: 0; }", 724 " PRE { padding: 0; margin: 10px 0 10px 20px; background: #eee; width: 45em; }", 725 " BLOCKQUOTE { margin: 10px 0; margin-left: 20px; }", 726 " LI BLOCKQUOTE { margin-left: 0; }", 727 " UL LI { list-style-type: square; }", 728 " .todo { color: darkred; text-align: right; }", 729 " .error { color: red; font-weight: bold; }", 730 " .warn { color: #ee0000; font-weight: bold; }", 731 " DIV.doctitle { margin-top: -13px;", 732 " font-size: 22px; line-height: 40px; font-weight: bold; }", 733 " DIV.twarn { color: #cc0000; font-weight: bold; margin-bottom: 9px; }", 734 " DIV.subtitle { margin-top: 2px; font-size: 18px; font-weight: bold; }", 735 " DIV.authors { margin-top: 10px; margin-bottom: 10px; font-size: 16px; }", 736 " DIV.author A { font-style: italic; }", 737 " DIV.version { margin-top: 10px; font-size: 12px; }", 738 " DIV.version, DIV.legal-notice { font-size: 12px; line-height: 15px; }", 739 " SPAN.hash { font-size: 9px; }", 740 " DIV.version SPAN.modified { color: green; font-weight: bold; }", 741 " DIV.head { margin-bottom: 20px; }", 742 " DIV.section > DIV.title, DIV.section DIV.number SPAN {", 743 " font-size: 15px; font-weight: bold; }", 744 " TABLE { border-collapse: collapse; border: none; }", 745 " TD.number { text-align: right; }", 746 " TD, TH { text-align: left; white-space: nowrap; }", 747 " TD.name, SPAN.name { font-weight: bold; }", 748 " ", 749 " TABLE.module { width: 100%; }", 750 " TABLE.module TD:first-child { padding-right: 10px; }", 751 " TR.module > TD { padding: 10px 0; border-top: 1px solid black; }", 752 " TR > TH { padding-bottom: 10px; }", 753 " TR.br TD { padding-top: 20px; }", 754 " TABLE.modules { margin-top: 20px; }", 755 " TABLE.modules > TBODY > TR > TD:nth-child(even) { background: #eee; }", 756 " TABLE.modules > TBODY > TR > TD, TABLE.modules > TBODY > TR > TH {", 757 " padding-left: 10px; padding-right: 10px; }", 758 " .reexp, .def { font-weight: bold; }", 759 " .agg { font-style: italic; }", 760 " SUP { height: 0; line-height: 1; position: relative;", 761 " vertical-align: baseline; bottom: 1ex; font-size: 11px; }", 762 "</style>", 763 }; 764 } 765 }