diff --git a/.gitignore b/.gitignore
index 0500d8a..a039e0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,8 @@
# Generated files
.docusaurus
.cache-loader
+
+# External files loaded by fetch-readmes script
/docs/js-sdk/api
/docs/js-sdk/examples/quickstart/*.ts
/docs/js-sdk/examples/view-manifest/*.ts
@@ -23,6 +25,7 @@
/docs/rust-sdk/*.md
/docs/rust-sdk/docs/*.md
/docs/**/readme.md
+/static/sb-alg-list.json
# Misc
.DS_Store
diff --git a/docs/soft-bindings.mdx b/docs/soft-bindings.mdx
new file mode 100644
index 0000000..78693a2
--- /dev/null
+++ b/docs/soft-bindings.mdx
@@ -0,0 +1,23 @@
+---
+id: sb-algs
+title: Watermarking and fingerprinting algorithms
+hide_table_of_contents: true
+---
+
+[_Durable Content Credentials_](https://contentauthenticity.org/blog/durable-content-credentials) is a concept that helps content provenance to persist across content platforms by also:
+
+- **Invisible watermarks**, actively inserted into the content
+- **Content fingerprinting**, passively computed from the content.
+
+Platforms might remove C2PA manifest data, if, for example, they use software that does not yet support the standard. By storing a copy of the manifest data in an online database, you can use a watermark or a fingerprint to find it again.
+Combining both watermark and fingerprints further improves the robustness of the provenance information.
+
+In the C2PA specification, watermarks and content fingerprinting are known as [soft bindings](https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_soft_bindings), which can be used to find digital content, even if the underlying bits differ. The C2PA specification requires that soft bindings be generated using one of the [algorithms](https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_soft_binding_algorithm_list) approved by the C2PA Technical Working Group. The following table lists the algorithms from the [C2PA Soft Binding Algorithm List](https://github.com/c2pa-org/softbinding-algorithm-list).
+
+:::note
+The table below is provided **for information only** and is created and automatically updated based on data from the C2PA [softbinding-algorithm-list](https://github.com/c2pa-org/softbinding-algorithm-list/blob/main/softbinding-algorithm-list.json) repository, which is the single authoritative source of the information.
+:::
+
+import JSONToTable from '@site/src/components/JSONToTable';
+
+
diff --git a/scripts/fetch-readme.js b/scripts/fetch-readme.js
index 850add6..903baf2 100644
--- a/scripts/fetch-readme.js
+++ b/scripts/fetch-readme.js
@@ -189,6 +189,11 @@ const readmes = [
repo: 'contentauth/c2pa-min',
path: 'README.md',
},
+ {
+ dest: resolve(__dirname, '../static/sb-alg-list.json'),
+ repo: 'c2pa-org/softbinding-algorithm-list',
+ path: 'softbinding-algorithm-list.json',
+ },
];
function resolveMarkdownLinks(linkBase, content) {
diff --git a/sidebars.js b/sidebars.js
index 168ed59..e618ab0 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -259,8 +259,17 @@ const sidebars = {
id: 'faqs',
},
{
- type: 'doc',
- id: 'community-resources',
+ type: 'category',
+ label: 'Community resources',
+ link: { type: 'doc', id: 'community-resources' },
+ collapsed: true,
+ items: [
+ {
+ type: 'doc',
+ label: 'Watermarking and fingerprinting',
+ id: 'sb-algs',
+ },
+ ],
},
{
type: 'doc',
diff --git a/src/components/JSONToTable.js b/src/components/JSONToTable.js
new file mode 100644
index 0000000..2e13ef7
--- /dev/null
+++ b/src/components/JSONToTable.js
@@ -0,0 +1,130 @@
+import React, { useState, useEffect } from 'react';
+
+// Define the fields to display and their custom headers
+const DISPLAY_FIELDS = {
+ alg: 'Algorithm Name',
+ decodedMediaTypes: 'Media Types',
+ 'entryMetadata.informationalUrl': 'URL',
+ 'entryMetadata.contact': 'Contact',
+};
+
+// Helper function to safely extract nested values and flatten arrays
+const getNestedValue = (obj, path) => {
+ const value = path
+ .split('.')
+ .reduce(
+ (acc, key) => (acc && acc[key] !== undefined ? acc[key] : 'N/A'),
+ obj,
+ );
+ return Array.isArray(value) ? value.join(', ') : value; // Flatten arrays into a comma-separated string
+};
+
+// Function to extract and filter only the required fields
+const extractAndFilterObject = (obj) => {
+ let result = {};
+ for (const key in DISPLAY_FIELDS) {
+ result[key] = getNestedValue(obj, key);
+ }
+ return result;
+};
+
+// Helper function to capitalize each word in a string
+const capitalizeWords = (str) => {
+ return str
+ .split(' ')
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ');
+};
+
+const JSONToTable = () => {
+ const [jsonData, setJsonData] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ fetch('/sb-alg-list.json')
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error('Failed to fetch the JSON file');
+ }
+ return response.json();
+ })
+ .then((data) => {
+ setJsonData(data);
+ setError(null);
+ })
+ .catch((err) => {
+ setError(err.message);
+ setJsonData(null);
+ });
+ }, []);
+
+ // Function to group data by the 'type' field
+ const groupDataByType = (data) => {
+ return data.reduce((acc, item) => {
+ const type = getNestedValue(item, 'type');
+ if (!acc[type]) acc[type] = [];
+ acc[type].push(item);
+ return acc;
+ }, {});
+ };
+
+ const generateTableHeaders = () => {
+ return Object.keys(DISPLAY_FIELDS).map((key) => (
+
{DISPLAY_FIELDS[key]} |
+ ));
+ };
+
+ const generateTableRows = (data) => {
+ return data.map((item, rowIndex) => {
+ const filteredData = extractAndFilterObject(item);
+ const url = filteredData['entryMetadata.informationalUrl'];
+ const description = getNestedValue(item, 'entryMetadata.description');
+
+ return (
+
+ {Object.keys(DISPLAY_FIELDS).map((key, index) => (
+
+ {key === 'entryMetadata.informationalUrl' ? (
+ url !== 'N/A' && description !== 'N/A' ? (
+
+ {description}
+
+ ) : (
+ 'N/A'
+ )
+ ) : (
+ filteredData[key]
+ )}
+ |
+ ))}
+
+ );
+ });
+ };
+
+ if (error) return {error}
;
+
+ if (!jsonData || !Array.isArray(jsonData)) {
+ return No data found in the JSON. Expected an array of objects.
;
+ }
+
+ const groupedData = groupDataByType(jsonData);
+
+ return (
+
+ {Object.entries(groupedData).map(([type, data]) => (
+
+
{capitalizeWords(type)} algorithms
+
+
+ {generateTableHeaders()}
+
+ {generateTableRows(data)}
+
+
+ ))}
+
+ );
+};
+
+export default JSONToTable;