benchstat: use safehtml/template, add SafeFormatHTML method

Add a version of the FormatHTML method returning a safehtml.HTML,
for use with safehtml templates.

Change-Id: I51b514e64479c289df3d797b94a71ad0524fdb4a
Reviewed-on: https://go-review.googlesource.com/c/perf/+/429155
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Tatiana Bradley <tatiana@golang.org>
diff --git a/benchstat/html.go b/benchstat/html.go
index eea80df..9e244ad 100644
--- a/benchstat/html.go
+++ b/benchstat/html.go
@@ -6,8 +6,10 @@
 
 import (
 	"bytes"
-	"html/template"
 	"strings"
+
+	"github.com/google/safehtml"
+	"github.com/google/safehtml/template"
 )
 
 var htmlTemplate = template.Must(template.New("").Funcs(htmlFuncs).Parse(`
@@ -75,6 +77,17 @@
 	return
 }
 
+// SafeFormatHTML returns the HTML formatting of the tables.
+func SafeFormatHTML(tables []*Table) safehtml.HTML {
+	h, err := htmlTemplate.ExecuteToHTML(tables)
+	if err != nil {
+		// Only possible errors here are template not matching data structure.
+		// Don't make caller check - it's our fault.
+		panic(err)
+	}
+	return h
+}
+
 // FormatHTML appends an HTML formatting of the tables to buf.
 func FormatHTML(buf *bytes.Buffer, tables []*Table) {
 	err := htmlTemplate.Execute(buf, tables)
diff --git a/cmd/benchstat/testdata/examplehtml.golden b/cmd/benchstat/testdata/examplehtml.golden
index 501dd57..6733791 100644
--- a/cmd/benchstat/testdata/examplehtml.golden
+++ b/cmd/benchstat/testdata/examplehtml.golden
@@ -21,15 +21,15 @@
 
 <tbody>
 <tr><th><th colspan='2' class='metric'>time/op<th>delta
-<tr class='better'><td>GobEncode<td>13.6ms ± 1%<td>11.8ms ± 1%<td class='delta'>−13.31%<td class='note'>(p=0.016 n=4&#43;5)
-<tr class='unchanged'><td>JSONEncode<td>32.1ms ± 1%<td>31.8ms ± 1%<td class='nodelta'>~<td class='note'>(p=0.286 n=4&#43;5)
+<tr class='better'><td>GobEncode<td>13.6ms ± 1%<td>11.8ms ± 1%<td class='delta'>−13.31%<td class='note'>(p=0.016 n=4+5)
+<tr class='unchanged'><td>JSONEncode<td>32.1ms ± 1%<td>31.8ms ± 1%<td class='nodelta'>~<td class='note'>(p=0.286 n=4+5)
 <tr><td>&nbsp;
 </tbody>
 
 <tbody>
 <tr><th><th colspan='2' class='metric'>speed<th>delta
-<tr class='better'><td>GobEncode<td>56.4MB/s ± 1%<td>65.1MB/s ± 1%<td class='delta'>&#43;15.36%<td class='note'>(p=0.016 n=4&#43;5)
-<tr class='unchanged'><td>JSONEncode<td>60.4MB/s ± 1%<td>61.1MB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.286 n=4&#43;5)
+<tr class='better'><td>GobEncode<td>56.4MB/s ± 1%<td>65.1MB/s ± 1%<td class='delta'>+15.36%<td class='note'>(p=0.016 n=4+5)
+<tr class='unchanged'><td>JSONEncode<td>60.4MB/s ± 1%<td>61.1MB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.286 n=4+5)
 <tr><td>&nbsp;
 </tbody>
 
diff --git a/cmd/benchstat/testdata/oldnewhtml.golden b/cmd/benchstat/testdata/oldnewhtml.golden
index 84ed364..9ac9367 100644
--- a/cmd/benchstat/testdata/oldnewhtml.golden
+++ b/cmd/benchstat/testdata/oldnewhtml.golden
@@ -21,83 +21,83 @@
 
 <tbody>
 <tr><th><th colspan='2' class='metric'>time/op<th>delta
-<tr class='better'><td>CRC32/poly=IEEE/size=15/align=0-8<td>46.9ns ± 8%<td>44.5ns ± 3%<td class='delta'>−5.01%<td class='note'>(p=0.008 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=IEEE/size=15/align=1-8<td>44.7ns ± 5%<td>44.5ns ± 4%<td class='nodelta'>~<td class='note'>(p=0.539 n=10&#43;10)
-<tr class='worse'><td>CRC32/poly=IEEE/size=40/align=0-8<td>41.0ns ± 1%<td>42.5ns ± 6%<td class='delta'>&#43;3.56%<td class='note'>(p=0.000 n=8&#43;10)
-<tr class='worse'><td>CRC32/poly=IEEE/size=40/align=1-8<td>41.1ns ± 1%<td>42.0ns ± 3%<td class='delta'>&#43;2.34%<td class='note'>(p=0.000 n=9&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=512/align=0-8<td>238ns ± 5%<td>57ns ± 3%<td class='delta'>−76.00%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=512/align=1-8<td>236ns ± 3%<td>57ns ± 3%<td class='delta'>−75.72%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=1kB/align=0-8<td>452ns ± 4%<td>94ns ± 2%<td class='delta'>−79.20%<td class='note'>(p=0.000 n=10&#43;8)
-<tr class='better'><td>CRC32/poly=IEEE/size=1kB/align=1-8<td>444ns ± 2%<td>93ns ± 2%<td class='delta'>−78.97%<td class='note'>(p=0.000 n=10&#43;8)
-<tr class='better'><td>CRC32/poly=IEEE/size=4kB/align=0-8<td>1.74µs ± 8%<td>0.30µs ± 1%<td class='delta'>−82.87%<td class='note'>(p=0.000 n=10&#43;9)
-<tr class='better'><td>CRC32/poly=IEEE/size=4kB/align=1-8<td>1.76µs ± 6%<td>0.30µs ± 3%<td class='delta'>−83.05%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=32kB/align=0-8<td>15.0µs ± 7%<td>2.2µs ± 3%<td class='delta'>−85.57%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=32kB/align=1-8<td>14.2µs ± 7%<td>2.2µs ± 3%<td class='delta'>−84.65%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=15/align=0-8<td>16.4ns ± 3%<td>16.3ns ± 2%<td class='nodelta'>~<td class='note'>(p=0.615 n=9&#43;9)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=15/align=1-8<td>17.2ns ± 2%<td>17.3ns ± 2%<td class='nodelta'>~<td class='note'>(p=0.650 n=9&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=40/align=0-8<td>17.4ns ± 2%<td>17.5ns ± 4%<td class='nodelta'>~<td class='note'>(p=0.694 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=Castagnoli/size=40/align=1-8<td>19.7ns ± 3%<td>19.4ns ± 2%<td class='delta'>−1.62%<td class='note'>(p=0.036 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=512/align=0-8<td>40.2ns ± 2%<td>40.1ns ± 4%<td class='nodelta'>~<td class='note'>(p=0.614 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=512/align=1-8<td>42.1ns ± 3%<td>41.9ns ± 2%<td class='nodelta'>~<td class='note'>(p=0.952 n=10&#43;9)
-<tr class='worse'><td>CRC32/poly=Castagnoli/size=1kB/align=0-8<td>65.5ns ± 1%<td>66.2ns ± 1%<td class='delta'>&#43;1.01%<td class='note'>(p=0.003 n=9&#43;8)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=1kB/align=1-8<td>70.1ns ± 6%<td>68.5ns ± 2%<td class='nodelta'>~<td class='note'>(p=0.190 n=10&#43;9)
-<tr class='better'><td>CRC32/poly=Castagnoli/size=4kB/align=0-8<td>163ns ± 5%<td>159ns ± 3%<td class='delta'>−2.46%<td class='note'>(p=0.032 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=Castagnoli/size=4kB/align=1-8<td>169ns ± 6%<td>162ns ± 3%<td class='delta'>−4.60%<td class='note'>(p=0.005 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=32kB/align=0-8<td>1.22µs ± 4%<td>1.21µs ± 3%<td class='nodelta'>~<td class='note'>(p=0.882 n=9&#43;9)
-<tr class='better'><td>CRC32/poly=Castagnoli/size=32kB/align=1-8<td>1.26µs ± 3%<td>1.22µs ± 4%<td class='delta'>−3.48%<td class='note'>(p=0.002 n=9&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=15/align=0-8<td>36.5ns ±11%<td>35.6ns ± 3%<td class='nodelta'>~<td class='note'>(p=0.216 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=15/align=1-8<td>35.1ns ± 5%<td>35.5ns ± 1%<td class='nodelta'>~<td class='note'>(p=0.508 n=10&#43;9)
-<tr class='better'><td>CRC32/poly=Koopman/size=40/align=0-8<td>91.6ns ± 9%<td>87.6ns ± 2%<td class='delta'>−4.35%<td class='note'>(p=0.002 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=40/align=1-8<td>91.1ns ± 6%<td>88.0ns ± 3%<td class='nodelta'>~<td class='note'>(p=0.055 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=Koopman/size=512/align=0-8<td>1.13µs ± 5%<td>1.08µs ± 3%<td class='delta'>−4.93%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=512/align=1-8<td>1.13µs ± 6%<td>1.17µs ± 8%<td class='nodelta'>~<td class='note'>(p=0.143 n=10&#43;10)
-<tr class='worse'><td>CRC32/poly=Koopman/size=1kB/align=0-8<td>2.24µs ± 6%<td>2.34µs ± 4%<td class='delta'>&#43;4.34%<td class='note'>(p=0.010 n=9&#43;10)
-<tr class='worse'><td>CRC32/poly=Koopman/size=1kB/align=1-8<td>2.15µs ± 2%<td>2.36µs ± 5%<td class='delta'>&#43;9.84%<td class='note'>(p=0.000 n=9&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=4kB/align=0-8<td>9.03µs ± 6%<td>9.00µs ± 6%<td class='nodelta'>~<td class='note'>(p=0.971 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=4kB/align=1-8<td>8.94µs ±10%<td>9.05µs ±12%<td class='nodelta'>~<td class='note'>(p=0.754 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=32kB/align=0-8<td>72.4µs ± 9%<td>72.9µs ± 4%<td class='nodelta'>~<td class='note'>(p=0.684 n=10&#43;10)
-<tr class='worse'><td>CRC32/poly=Koopman/size=32kB/align=1-8<td>69.6µs ± 3%<td>74.3µs ± 3%<td class='delta'>&#43;6.70%<td class='note'>(p=0.000 n=8&#43;10)
+<tr class='better'><td>CRC32/poly=IEEE/size=15/align=0-8<td>46.9ns ± 8%<td>44.5ns ± 3%<td class='delta'>−5.01%<td class='note'>(p=0.008 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=IEEE/size=15/align=1-8<td>44.7ns ± 5%<td>44.5ns ± 4%<td class='nodelta'>~<td class='note'>(p=0.539 n=10+10)
+<tr class='worse'><td>CRC32/poly=IEEE/size=40/align=0-8<td>41.0ns ± 1%<td>42.5ns ± 6%<td class='delta'>+3.56%<td class='note'>(p=0.000 n=8+10)
+<tr class='worse'><td>CRC32/poly=IEEE/size=40/align=1-8<td>41.1ns ± 1%<td>42.0ns ± 3%<td class='delta'>+2.34%<td class='note'>(p=0.000 n=9+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=512/align=0-8<td>238ns ± 5%<td>57ns ± 3%<td class='delta'>−76.00%<td class='note'>(p=0.000 n=10+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=512/align=1-8<td>236ns ± 3%<td>57ns ± 3%<td class='delta'>−75.72%<td class='note'>(p=0.000 n=10+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=1kB/align=0-8<td>452ns ± 4%<td>94ns ± 2%<td class='delta'>−79.20%<td class='note'>(p=0.000 n=10+8)
+<tr class='better'><td>CRC32/poly=IEEE/size=1kB/align=1-8<td>444ns ± 2%<td>93ns ± 2%<td class='delta'>−78.97%<td class='note'>(p=0.000 n=10+8)
+<tr class='better'><td>CRC32/poly=IEEE/size=4kB/align=0-8<td>1.74µs ± 8%<td>0.30µs ± 1%<td class='delta'>−82.87%<td class='note'>(p=0.000 n=10+9)
+<tr class='better'><td>CRC32/poly=IEEE/size=4kB/align=1-8<td>1.76µs ± 6%<td>0.30µs ± 3%<td class='delta'>−83.05%<td class='note'>(p=0.000 n=10+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=32kB/align=0-8<td>15.0µs ± 7%<td>2.2µs ± 3%<td class='delta'>−85.57%<td class='note'>(p=0.000 n=10+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=32kB/align=1-8<td>14.2µs ± 7%<td>2.2µs ± 3%<td class='delta'>−84.65%<td class='note'>(p=0.000 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=15/align=0-8<td>16.4ns ± 3%<td>16.3ns ± 2%<td class='nodelta'>~<td class='note'>(p=0.615 n=9+9)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=15/align=1-8<td>17.2ns ± 2%<td>17.3ns ± 2%<td class='nodelta'>~<td class='note'>(p=0.650 n=9+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=40/align=0-8<td>17.4ns ± 2%<td>17.5ns ± 4%<td class='nodelta'>~<td class='note'>(p=0.694 n=10+10)
+<tr class='better'><td>CRC32/poly=Castagnoli/size=40/align=1-8<td>19.7ns ± 3%<td>19.4ns ± 2%<td class='delta'>−1.62%<td class='note'>(p=0.036 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=512/align=0-8<td>40.2ns ± 2%<td>40.1ns ± 4%<td class='nodelta'>~<td class='note'>(p=0.614 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=512/align=1-8<td>42.1ns ± 3%<td>41.9ns ± 2%<td class='nodelta'>~<td class='note'>(p=0.952 n=10+9)
+<tr class='worse'><td>CRC32/poly=Castagnoli/size=1kB/align=0-8<td>65.5ns ± 1%<td>66.2ns ± 1%<td class='delta'>+1.01%<td class='note'>(p=0.003 n=9+8)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=1kB/align=1-8<td>70.1ns ± 6%<td>68.5ns ± 2%<td class='nodelta'>~<td class='note'>(p=0.190 n=10+9)
+<tr class='better'><td>CRC32/poly=Castagnoli/size=4kB/align=0-8<td>163ns ± 5%<td>159ns ± 3%<td class='delta'>−2.46%<td class='note'>(p=0.032 n=10+10)
+<tr class='better'><td>CRC32/poly=Castagnoli/size=4kB/align=1-8<td>169ns ± 6%<td>162ns ± 3%<td class='delta'>−4.60%<td class='note'>(p=0.005 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=32kB/align=0-8<td>1.22µs ± 4%<td>1.21µs ± 3%<td class='nodelta'>~<td class='note'>(p=0.882 n=9+9)
+<tr class='better'><td>CRC32/poly=Castagnoli/size=32kB/align=1-8<td>1.26µs ± 3%<td>1.22µs ± 4%<td class='delta'>−3.48%<td class='note'>(p=0.002 n=9+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=15/align=0-8<td>36.5ns ±11%<td>35.6ns ± 3%<td class='nodelta'>~<td class='note'>(p=0.216 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=15/align=1-8<td>35.1ns ± 5%<td>35.5ns ± 1%<td class='nodelta'>~<td class='note'>(p=0.508 n=10+9)
+<tr class='better'><td>CRC32/poly=Koopman/size=40/align=0-8<td>91.6ns ± 9%<td>87.6ns ± 2%<td class='delta'>−4.35%<td class='note'>(p=0.002 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=40/align=1-8<td>91.1ns ± 6%<td>88.0ns ± 3%<td class='nodelta'>~<td class='note'>(p=0.055 n=10+10)
+<tr class='better'><td>CRC32/poly=Koopman/size=512/align=0-8<td>1.13µs ± 5%<td>1.08µs ± 3%<td class='delta'>−4.93%<td class='note'>(p=0.000 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=512/align=1-8<td>1.13µs ± 6%<td>1.17µs ± 8%<td class='nodelta'>~<td class='note'>(p=0.143 n=10+10)
+<tr class='worse'><td>CRC32/poly=Koopman/size=1kB/align=0-8<td>2.24µs ± 6%<td>2.34µs ± 4%<td class='delta'>+4.34%<td class='note'>(p=0.010 n=9+10)
+<tr class='worse'><td>CRC32/poly=Koopman/size=1kB/align=1-8<td>2.15µs ± 2%<td>2.36µs ± 5%<td class='delta'>+9.84%<td class='note'>(p=0.000 n=9+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=4kB/align=0-8<td>9.03µs ± 6%<td>9.00µs ± 6%<td class='nodelta'>~<td class='note'>(p=0.971 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=4kB/align=1-8<td>8.94µs ±10%<td>9.05µs ±12%<td class='nodelta'>~<td class='note'>(p=0.754 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=32kB/align=0-8<td>72.4µs ± 9%<td>72.9µs ± 4%<td class='nodelta'>~<td class='note'>(p=0.684 n=10+10)
+<tr class='worse'><td>CRC32/poly=Koopman/size=32kB/align=1-8<td>69.6µs ± 3%<td>74.3µs ± 3%<td class='delta'>+6.70%<td class='note'>(p=0.000 n=8+10)
 <tr><td>&nbsp;
 </tbody>
 
 <tbody>
 <tr><th><th colspan='2' class='metric'>speed<th>delta
-<tr class='better'><td>CRC32/poly=IEEE/size=15/align=0-8<td>321MB/s ± 8%<td>337MB/s ± 3%<td class='delta'>&#43;5.06%<td class='note'>(p=0.009 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=IEEE/size=15/align=1-8<td>336MB/s ± 4%<td>337MB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.579 n=10&#43;10)
-<tr class='worse'><td>CRC32/poly=IEEE/size=40/align=0-8<td>975MB/s ± 1%<td>942MB/s ± 5%<td class='delta'>−3.37%<td class='note'>(p=0.001 n=8&#43;10)
-<tr class='worse'><td>CRC32/poly=IEEE/size=40/align=1-8<td>974MB/s ± 1%<td>952MB/s ± 3%<td class='delta'>−2.25%<td class='note'>(p=0.000 n=9&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=512/align=0-8<td>2.15GB/s ± 4%<td>8.97GB/s ± 3%<td class='delta'>&#43;317.65%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=512/align=1-8<td>2.17GB/s ± 3%<td>8.96GB/s ± 3%<td class='delta'>&#43;312.89%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=1kB/align=0-8<td>2.26GB/s ± 4%<td>10.88GB/s ± 2%<td class='delta'>&#43;381.12%<td class='note'>(p=0.000 n=10&#43;8)
-<tr class='better'><td>CRC32/poly=IEEE/size=1kB/align=1-8<td>2.31GB/s ± 2%<td>10.98GB/s ± 2%<td class='delta'>&#43;375.97%<td class='note'>(p=0.000 n=10&#43;8)
-<tr class='better'><td>CRC32/poly=IEEE/size=4kB/align=0-8<td>2.36GB/s ± 7%<td>13.73GB/s ± 1%<td class='delta'>&#43;482.26%<td class='note'>(p=0.000 n=10&#43;9)
-<tr class='better'><td>CRC32/poly=IEEE/size=4kB/align=1-8<td>2.33GB/s ± 6%<td>13.68GB/s ± 3%<td class='delta'>&#43;488.23%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=32kB/align=0-8<td>2.19GB/s ± 7%<td>15.19GB/s ± 3%<td class='delta'>&#43;591.99%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=IEEE/size=32kB/align=1-8<td>2.31GB/s ± 8%<td>15.04GB/s ± 3%<td class='delta'>&#43;550.07%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=15/align=0-8<td>916MB/s ± 2%<td>920MB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.489 n=9&#43;9)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=15/align=1-8<td>870MB/s ± 2%<td>867MB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.661 n=9&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=40/align=0-8<td>2.30GB/s ± 2%<td>2.28GB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.684 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=40/align=1-8<td>2.03GB/s ± 3%<td>2.06GB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.063 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=512/align=0-8<td>12.7GB/s ± 2%<td>12.8GB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.529 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=512/align=1-8<td>12.1GB/s ± 3%<td>12.2GB/s ± 1%<td class='nodelta'>~<td class='note'>(p=0.780 n=10&#43;9)
-<tr class='worse'><td>CRC32/poly=Castagnoli/size=1kB/align=0-8<td>15.6GB/s ± 1%<td>15.5GB/s ± 1%<td class='delta'>−1.02%<td class='note'>(p=0.002 n=9&#43;8)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=1kB/align=1-8<td>14.6GB/s ± 6%<td>15.0GB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.211 n=10&#43;9)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=4kB/align=0-8<td>25.1GB/s ± 5%<td>25.7GB/s ± 3%<td class='nodelta'>~<td class='note'>(p=0.052 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=Castagnoli/size=4kB/align=1-8<td>24.1GB/s ± 6%<td>25.3GB/s ± 3%<td class='delta'>&#43;4.71%<td class='note'>(p=0.005 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=32kB/align=0-8<td>26.9GB/s ± 4%<td>26.8GB/s ± 5%<td class='nodelta'>~<td class='note'>(p=0.842 n=9&#43;10)
-<tr class='better'><td>CRC32/poly=Castagnoli/size=32kB/align=1-8<td>25.9GB/s ± 3%<td>26.8GB/s ± 4%<td class='delta'>&#43;3.62%<td class='note'>(p=0.002 n=9&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=15/align=0-8<td>412MB/s ±10%<td>421MB/s ± 3%<td class='nodelta'>~<td class='note'>(p=0.218 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=15/align=1-8<td>427MB/s ± 5%<td>422MB/s ± 1%<td class='nodelta'>~<td class='note'>(p=0.497 n=10&#43;9)
-<tr class='better'><td>CRC32/poly=Koopman/size=40/align=0-8<td>437MB/s ± 9%<td>456MB/s ± 2%<td class='delta'>&#43;4.50%<td class='note'>(p=0.002 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=40/align=1-8<td>440MB/s ± 6%<td>455MB/s ± 3%<td class='nodelta'>~<td class='note'>(p=0.052 n=10&#43;10)
-<tr class='better'><td>CRC32/poly=Koopman/size=512/align=0-8<td>453MB/s ± 5%<td>476MB/s ± 3%<td class='delta'>&#43;5.09%<td class='note'>(p=0.000 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=512/align=1-8<td>455MB/s ± 6%<td>440MB/s ± 8%<td class='nodelta'>~<td class='note'>(p=0.143 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=1kB/align=0-8<td>452MB/s ± 9%<td>438MB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.052 n=10&#43;10)
-<tr class='worse'><td>CRC32/poly=Koopman/size=1kB/align=1-8<td>477MB/s ± 2%<td>434MB/s ± 5%<td class='delta'>−8.92%<td class='note'>(p=0.000 n=9&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=4kB/align=0-8<td>454MB/s ± 5%<td>455MB/s ± 6%<td class='nodelta'>~<td class='note'>(p=0.971 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=4kB/align=1-8<td>459MB/s ± 9%<td>455MB/s ±11%<td class='nodelta'>~<td class='note'>(p=0.739 n=10&#43;10)
-<tr class='unchanged'><td>CRC32/poly=Koopman/size=32kB/align=0-8<td>453MB/s ± 8%<td>450MB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.684 n=10&#43;10)
-<tr class='worse'><td>CRC32/poly=Koopman/size=32kB/align=1-8<td>471MB/s ± 3%<td>441MB/s ± 3%<td class='delta'>−6.25%<td class='note'>(p=0.000 n=8&#43;10)
+<tr class='better'><td>CRC32/poly=IEEE/size=15/align=0-8<td>321MB/s ± 8%<td>337MB/s ± 3%<td class='delta'>+5.06%<td class='note'>(p=0.009 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=IEEE/size=15/align=1-8<td>336MB/s ± 4%<td>337MB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.579 n=10+10)
+<tr class='worse'><td>CRC32/poly=IEEE/size=40/align=0-8<td>975MB/s ± 1%<td>942MB/s ± 5%<td class='delta'>−3.37%<td class='note'>(p=0.001 n=8+10)
+<tr class='worse'><td>CRC32/poly=IEEE/size=40/align=1-8<td>974MB/s ± 1%<td>952MB/s ± 3%<td class='delta'>−2.25%<td class='note'>(p=0.000 n=9+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=512/align=0-8<td>2.15GB/s ± 4%<td>8.97GB/s ± 3%<td class='delta'>+317.65%<td class='note'>(p=0.000 n=10+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=512/align=1-8<td>2.17GB/s ± 3%<td>8.96GB/s ± 3%<td class='delta'>+312.89%<td class='note'>(p=0.000 n=10+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=1kB/align=0-8<td>2.26GB/s ± 4%<td>10.88GB/s ± 2%<td class='delta'>+381.12%<td class='note'>(p=0.000 n=10+8)
+<tr class='better'><td>CRC32/poly=IEEE/size=1kB/align=1-8<td>2.31GB/s ± 2%<td>10.98GB/s ± 2%<td class='delta'>+375.97%<td class='note'>(p=0.000 n=10+8)
+<tr class='better'><td>CRC32/poly=IEEE/size=4kB/align=0-8<td>2.36GB/s ± 7%<td>13.73GB/s ± 1%<td class='delta'>+482.26%<td class='note'>(p=0.000 n=10+9)
+<tr class='better'><td>CRC32/poly=IEEE/size=4kB/align=1-8<td>2.33GB/s ± 6%<td>13.68GB/s ± 3%<td class='delta'>+488.23%<td class='note'>(p=0.000 n=10+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=32kB/align=0-8<td>2.19GB/s ± 7%<td>15.19GB/s ± 3%<td class='delta'>+591.99%<td class='note'>(p=0.000 n=10+10)
+<tr class='better'><td>CRC32/poly=IEEE/size=32kB/align=1-8<td>2.31GB/s ± 8%<td>15.04GB/s ± 3%<td class='delta'>+550.07%<td class='note'>(p=0.000 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=15/align=0-8<td>916MB/s ± 2%<td>920MB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.489 n=9+9)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=15/align=1-8<td>870MB/s ± 2%<td>867MB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.661 n=9+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=40/align=0-8<td>2.30GB/s ± 2%<td>2.28GB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.684 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=40/align=1-8<td>2.03GB/s ± 3%<td>2.06GB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.063 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=512/align=0-8<td>12.7GB/s ± 2%<td>12.8GB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.529 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=512/align=1-8<td>12.1GB/s ± 3%<td>12.2GB/s ± 1%<td class='nodelta'>~<td class='note'>(p=0.780 n=10+9)
+<tr class='worse'><td>CRC32/poly=Castagnoli/size=1kB/align=0-8<td>15.6GB/s ± 1%<td>15.5GB/s ± 1%<td class='delta'>−1.02%<td class='note'>(p=0.002 n=9+8)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=1kB/align=1-8<td>14.6GB/s ± 6%<td>15.0GB/s ± 2%<td class='nodelta'>~<td class='note'>(p=0.211 n=10+9)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=4kB/align=0-8<td>25.1GB/s ± 5%<td>25.7GB/s ± 3%<td class='nodelta'>~<td class='note'>(p=0.052 n=10+10)
+<tr class='better'><td>CRC32/poly=Castagnoli/size=4kB/align=1-8<td>24.1GB/s ± 6%<td>25.3GB/s ± 3%<td class='delta'>+4.71%<td class='note'>(p=0.005 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Castagnoli/size=32kB/align=0-8<td>26.9GB/s ± 4%<td>26.8GB/s ± 5%<td class='nodelta'>~<td class='note'>(p=0.842 n=9+10)
+<tr class='better'><td>CRC32/poly=Castagnoli/size=32kB/align=1-8<td>25.9GB/s ± 3%<td>26.8GB/s ± 4%<td class='delta'>+3.62%<td class='note'>(p=0.002 n=9+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=15/align=0-8<td>412MB/s ±10%<td>421MB/s ± 3%<td class='nodelta'>~<td class='note'>(p=0.218 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=15/align=1-8<td>427MB/s ± 5%<td>422MB/s ± 1%<td class='nodelta'>~<td class='note'>(p=0.497 n=10+9)
+<tr class='better'><td>CRC32/poly=Koopman/size=40/align=0-8<td>437MB/s ± 9%<td>456MB/s ± 2%<td class='delta'>+4.50%<td class='note'>(p=0.002 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=40/align=1-8<td>440MB/s ± 6%<td>455MB/s ± 3%<td class='nodelta'>~<td class='note'>(p=0.052 n=10+10)
+<tr class='better'><td>CRC32/poly=Koopman/size=512/align=0-8<td>453MB/s ± 5%<td>476MB/s ± 3%<td class='delta'>+5.09%<td class='note'>(p=0.000 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=512/align=1-8<td>455MB/s ± 6%<td>440MB/s ± 8%<td class='nodelta'>~<td class='note'>(p=0.143 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=1kB/align=0-8<td>452MB/s ± 9%<td>438MB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.052 n=10+10)
+<tr class='worse'><td>CRC32/poly=Koopman/size=1kB/align=1-8<td>477MB/s ± 2%<td>434MB/s ± 5%<td class='delta'>−8.92%<td class='note'>(p=0.000 n=9+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=4kB/align=0-8<td>454MB/s ± 5%<td>455MB/s ± 6%<td class='nodelta'>~<td class='note'>(p=0.971 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=4kB/align=1-8<td>459MB/s ± 9%<td>455MB/s ±11%<td class='nodelta'>~<td class='note'>(p=0.739 n=10+10)
+<tr class='unchanged'><td>CRC32/poly=Koopman/size=32kB/align=0-8<td>453MB/s ± 8%<td>450MB/s ± 4%<td class='nodelta'>~<td class='note'>(p=0.684 n=10+10)
+<tr class='worse'><td>CRC32/poly=Koopman/size=32kB/align=1-8<td>471MB/s ± 3%<td>441MB/s ± 3%<td class='delta'>−6.25%<td class='note'>(p=0.000 n=8+10)
 <tr><td>&nbsp;
 </tbody>
 
diff --git a/go.mod b/go.mod
index dc6b9f1..40d73e7 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@
 	github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 // indirect
 	github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 // indirect
 	github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 // indirect
+	github.com/google/safehtml v0.0.2
 	github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e // indirect
 	gonum.org/v1/plot v0.10.0
 	google.golang.org/grpc v0.0.0-20170208002647-2a6bf6142e96 // indirect
diff --git a/go.sum b/go.sum
index c13c0cc..2b35d56 100644
--- a/go.sum
+++ b/go.sum
@@ -48,6 +48,8 @@
 github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A=
 github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 h1:V2IgdyerlBa/MxaEFRbV5juy/C3MGdj4ePi+g6ePIp4=
 github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw=
+github.com/google/safehtml v0.0.2 h1:ZOt2VXg4x24bW0m2jtzAOkhoXV0iM8vNKc0paByCZqM=
+github.com/google/safehtml v0.0.2/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
 github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e h1:CYRpN206UTHUinz3VJoLaBdy1gEGeJNsqT0mvswDcMw=
 github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
 github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
@@ -98,6 +100,7 @@
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=