1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2026-10-23 21:28:56 +03:00

Show a card rather than the logo where a link is posted.

The image a link to the documentation showed where it was posted was the project logo at 96 by 96. The sites that show a preview lay one out in a frame of 1.91 to 1 and crop what does not fit, so a square image lost about a quarter of its height off the top and another off the bottom, and the logo was drawn edge to edge with nothing to give the crop. It was also well under the 1200 by 627 those sites ask for, so what survived the crop had been scaled up from too little to begin with.

The card is laid out at 1200 by 630, which is the shape the frame already is, and carries the header lockup -- the mark, the name and the tagline -- at 960 wide and centered. That is the middle 80 percent of the card, which also leaves the whole of it inside the 2 to 1 frame X crops to. The background is opaque because a site composites what is transparent onto a background of its own choosing, and dark artwork on a background nobody has said anything about is a poor bet.

The variable that names the image is project-card and the one that turns it off is card, since a 1200 by 630 preview image is not a logo. logo.png went with them, the preview being the only thing it was for, and it is the entry that changes in the linter skip list. logo.svg stays and is unaffected: it is the favicon, and it is the bare mark rather than the lockup.

A page now says how big the card is, what it shows, and that a preview of it is a card rather than a thumbnail beside the text. The size is read out of the file rather than written down beside it, so the two cannot disagree.

The html-logo variable is removed. It was never set, and the div it added was hidden by the style from the point the header began drawing the mark and the name itself, so it had been dead since the site was modernized.
This commit is contained in:
David Steele
2026-08-01 19:14:39 +07:00
parent 60b85c58d9
commit ab8df23807
10 changed files with 75 additions and 41 deletions

View File

@@ -10,6 +10,7 @@ change to the tool changed nothing about the documentation, and output that is a
import os
import re
import shutil
import struct
from common.error import ToolError
from common.log import *
@@ -74,11 +75,12 @@ class HtmlElement:
class HtmlBuilder:
"""Builds a page from the elements it holds."""
def __init__(self, name, title, favicon, logo, description):
def __init__(self, name, title, favicon, card, card_size, description):
self.name = name
self.title = title
self.favicon = favicon
self.logo = logo
self.card = card
self.card_size = card_size
self.description = description
self.body = HtmlElement("body")
@@ -149,9 +151,17 @@ class HtmlBuilder:
if self.favicon is not None:
result += '<link rel="icon" href="%s" type="image/svg+xml"></link>\n' % self.favicon
if self.logo is not None:
# The card is what a link to the page looks like where it is posted. It is laid out at 1200x630 because that is the frame
# the sites that show a preview crop to, and a card that is not that shape loses its top and bottom to the crop.
if self.card is not None:
result += '<meta property="og:image" content="{[backrest-url-base]}/%s"></meta>\n' % self.card
result += '<meta property="og:image:type" content="image/png"></meta>\n'
result += '<meta property="og:image" content="{[backrest-url-base]}/%s"></meta>\n' % self.logo
result += '<meta property="og:image:width" content="%d"></meta>\n' % self.card_size[0]
result += '<meta property="og:image:height" content="%d"></meta>\n' % self.card_size[1]
result += '<meta property="og:image:alt" content="%s"></meta>\n' % self._escape(self.name)
# Without this the preview is a thumbnail beside the text rather than the card above it
result += '<meta name="twitter:card" content="summary_large_image"></meta>\n'
if self.description is not None:
result += '<meta name="description" content="%s"></meta>\n' % self._escape(self.description)
@@ -193,19 +203,18 @@ class DocHtmlPage(DocExecute):
tagline = var_store.get("project-tagline")
card = None if var_store.test("card", "n") else var_store.get("project-card")
builder = HtmlBuilder(
var_store.replace_str("{[project]}" + ("" if tagline is None else " - " + tagline)),
var_store.replace_str(title + ("" if subtitle is None else " - " + subtitle)),
var_store.get("project-favicon"),
None if var_store.test("logo", "n") else var_store.get("project-logo"),
card,
None if card is None else _png_size(os.path.join(self.manifest.path_doc, "resource", card)),
var_store.replace_str(xml_node_field(self.root, "description", True).strip()),
)
header = builder.body.add_new("div", "page-header")
if var_store.get("html-logo") is not None:
header.add_new("div", "page-header-logo", content="{[html-logo]}")
header.add_new("div", "page-header-title", content=title)
if subtitle is not None:
@@ -534,6 +543,17 @@ class DocHtmlPage(DocExecute):
return element
####################################################################################################################################
def _png_size(file):
"""Read what a png says it measures.
The card says its size in the page so a crawler can lay the preview out before it has the image, and the size is read from the
image rather than written down so the two cannot disagree."""
with open(file, "rb") as handle:
return struct.unpack(">II", handle.read(24)[16:24])
####################################################################################################################################
def _resource_render(text, line_comment=False):
"""A style or a script with what it says about itself taken out.
@@ -602,10 +622,10 @@ def html_render(manifest, path_doc, path_out, exe):
if favicon is not None:
shutil.copyfile(os.path.join(path_doc, "resource", favicon), os.path.join(path_out, favicon))
logo = var_store.get("project-logo")
card = var_store.get("project-card")
if logo is not None and not var_store.test("logo", "n"):
shutil.copyfile(os.path.join(path_doc, "resource", logo), os.path.join(path_out, logo))
if card is not None and not var_store.test("card", "n"):
shutil.copyfile(os.path.join(path_doc, "resource", card), os.path.join(path_out, card))
if not var_store.test("sponsor", "n"):
path_sponsor = os.path.join(path_doc, "resource/sponsor")

View File

@@ -8,11 +8,11 @@
<variable key="project-url-root">/</variable>
<variable key="postgres">PostgreSQL</variable>
<variable key="dash">-</variable>
<variable key="project-logo">logo.png</variable>
<variable key="project-card">card.png</variable>
<variable key="project-favicon">logo.svg</variable>
<!-- Include logo? -->
<variable key="logo">y</variable>
<!-- Include the card that a link to the documentation shows where it is posted? -->
<variable key="card">y</variable>
<!-- Is this a development or debug build of the docs? -->
<variable key="dev">n</variable>
@@ -27,7 +27,6 @@
<!-- HTML variables -->
<variable key="html-footer">Copyright &amp;copy; 2015-{[release-year]}, The PostgreSQL Global Development Group, &lt;a href="{[github-url-license]}">MIT License&lt;/a>. Updated {[release-date]}</variable>
<!-- <variable key="html-logo">&lt;img src=&quot;{[project-logo]}&quot;&gt;</variable> -->
</variable-list>
<!-- A source with no file of its own is built rather than read, from the same declarations the binary is generated from -->

View File

@@ -309,7 +309,7 @@ def cmd_dist(config, dev):
"--var=project-url-root=index.html",
"--exclude=metric",
"--exclude=news",
"--var=logo=n",
"--var=card=n",
"--var=news=n",
"--var=sponsor=n",
]

BIN
doc/resource/card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -221,11 +221,6 @@ Header
font-size: 1.25rem;
}
.page-header-logo
{
display: none;
}
/***********************************************************************************************************************************
Menu
***********************************************************************************************************************************/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -17,7 +17,7 @@ from common.storage import path_list_recurse
# Files that are exempt from the content checks. A binary file is an unscannable place to hide content, so each entry must be a
# deliberate, reviewable decision.
_FILE_SKIP_LIST = (
"doc/resource/logo.png", # Project logo
"doc/resource/card.png", # Card shown where a link to the documentation is posted
"test/data/filecopy.table.bin", # Binary test fixture
"doc/resource/git-history.cache", # Generated from git history, contains non-ASCII author names
)

View File

@@ -5,6 +5,7 @@ variables the caller gave before the documents that use them."""
####################################################################################################################################
import os
import struct
import tempfile
from harness.test import *
@@ -122,7 +123,7 @@ MANIFEST = """<doc>
<variable key="project">pgBackRest</variable>
<variable key="project-exe">pgbackrest</variable>
<variable key="project-url-root">/</variable>
<variable key="project-logo">logo.png</variable>
<variable key="project-card">card.png</variable>
<variable key="project-favicon">logo.svg</variable>
<variable key="html-footer">Updated {[release-date]}</variable>
<variable key="mode">release</variable>
@@ -174,7 +175,11 @@ def _repo(path):
file_write(os.path.join(path, "doc/resource/html/default.css"), "body { color: black; }\n")
file_write(os.path.join(path, "doc/resource/html/default.js"), "// script\n")
file_write(os.path.join(path, "doc/resource/slogo.svg"), "<svg></svg>\n")
file_write(os.path.join(path, "doc/resource/logo.png"), "png")
# Only the header of the card is needed, since that is all that is read of it
file_write(
os.path.join(path, "doc/resource/card.png"),
b"\x89PNG\r\n\x1a\n" + struct.pack(">I", 13) + b"IHDR" + struct.pack(">II", 1200, 630),
)
file_write(os.path.join(path, "doc/resource/logo.svg"), "svg")
file_write(os.path.join(path, "doc/resource/sponsor/one.png"), "png")
@@ -306,7 +311,7 @@ def test_doc():
assert_equal(sorted(path_list(path_out)), ["html", "man", "markdown"])
assert_equal(
sorted(path_list(os.path.join(path_out, "html"))),
["default.css", "default.js", "index.html", "logo.png", "logo.svg", "slogo.svg", "sponsor", "user-guide.html"],
["card.png", "default.css", "default.js", "index.html", "logo.svg", "slogo.svg", "sponsor", "user-guide.html"],
)
# What goes beside the pages is copied rather than rendered

View File

@@ -5,6 +5,7 @@ linefeed goes is what makes one build of the documentation comparable with the n
####################################################################################################################################
import os
import struct
import tempfile
from harness.test import *
@@ -222,7 +223,7 @@ def _page(path, key="user-guide", config=None):
def test_element():
"""A page is built as a tree of elements so the code that decides what it holds never deals with markup."""
builder = HtmlBuilder("Name", "Title", None, None, None)
builder = HtmlBuilder("Name", "Title", None, None, None, None)
body = builder.body
body.add_new("div", "one", content="Content")
@@ -236,13 +237,20 @@ def test_element():
def test_builder():
"""A page says what it is."""
builder = HtmlBuilder("pgBackRest", "Guide & More", "logo.svg", "logo.png", "How to use it.")
builder = HtmlBuilder("pgBackRest", "Guide & More", "logo.svg", "card.png", (1200, 630), "How to use it.")
result = builder.render(analytics=True)
assert_in("<!DOCTYPE html PUBLIC", result)
assert_in('<link rel="icon" href="logo.svg" type="image/svg+xml"></link>', result)
assert_in('<meta property="og:image" content="{[backrest-url-base]}/logo.png"></meta>', result)
# The card says where it is and what shape it is, so a preview can be laid out before the image has been fetched
assert_in('<meta property="og:image" content="{[backrest-url-base]}/card.png"></meta>', result)
assert_in('<meta property="og:image:width" content="1200"></meta>', result)
assert_in('<meta property="og:image:height" content="630"></meta>', result)
assert_in('<meta property="og:image:alt" content="pgBackRest"></meta>', result)
assert_in('<meta name="twitter:card" content="summary_large_image"></meta>', result)
assert_in('<meta name="description" content="How to use it."></meta>', result)
assert_in('<link rel="stylesheet" href="default.css" type="text/css"></link>', result)
assert_in('<script src="default.js" defer="defer"></script>', result)
@@ -404,12 +412,17 @@ def test_html_render():
with tempfile.TemporaryDirectory() as path:
_doc_path(path)
file_write(os.path.join(path, "resource/logo.png"), "png")
# Only the header of the card is needed, since that is all that is read of it, and a size of its own shows the page says
# what the file measures rather than what the tool expects it to
file_write(
os.path.join(path, "resource/card.png"),
b"\x89PNG\r\n\x1a\n" + struct.pack(">I", 13) + b"IHDR" + struct.pack(">II", 400, 200),
)
file_write(os.path.join(path, "resource/logo.svg"), "svg")
file_write(os.path.join(path, "resource/sponsor/one.png"), "png")
manifest = _manifest(path)
manifest.var_store.add("project-logo", "logo.png")
manifest.var_store.add("project-card", "card.png")
manifest.var_store.add("project-favicon", "logo.svg")
path_out = os.path.join(path, "output/html")
@@ -419,10 +432,15 @@ def test_html_render():
assert_equal(
sorted(path_list(path_out)),
["default.css", "default.js", "index.html", "logo.png", "logo.svg", "slogo.svg", "sponsor", "user-guide.html"],
["card.png", "default.css", "default.js", "index.html", "logo.svg", "slogo.svg", "sponsor", "user-guide.html"],
)
assert_equal(path_list(os.path.join(path_out, "sponsor")), ["one.png"])
index = file_read(os.path.join(path_out, "index.html"))
assert_in('<meta property="og:image:width" content="400"></meta>', index)
assert_in('<meta property="og:image:height" content="200"></meta>', index)
# The style and the script carry no comments, since the page carries them for a browser rather than for a reader, but
# where their lines fall is left alone so one build can still be compared with the next
assert_equal(file_read(os.path.join(path_out, "default.css")), "body\n{\n color: black;\n}\n")
@@ -544,14 +562,10 @@ def test_page_other():
with tempfile.TemporaryDirectory() as path:
manifest = _manifest(_doc_path(path, user_guide=OTHER_GUIDE))
manifest.var_store.add("html-logo", "<img src='logo.png'>")
render = manifest.render_get(RENDER_HTML)
page = DocHtmlPage(manifest, "user-guide", render.menu, False).process()
# A logo of its own for the header
assert_in('<div class="page-header-logo">', page)
# A table with no header lines every column up left
assert_in('<td class="table-data-left">', page)
assert_not_in("table-header-row", page)
@@ -593,15 +607,16 @@ def test_page_postgres_empty():
####################################################################################################################################
def test_html_render_plain():
"""A build with no logo and no sponsors copies only the style."""
"""A build with no card and no sponsors copies only the style."""
with tempfile.TemporaryDirectory() as path:
_doc_path(path)
# The card is named but turned off, which is how the documentation that ships with the distribution is built
manifest = _manifest(path)
manifest.var_store.add("sponsor", "n")
manifest.var_store.add("logo", "n")
manifest.var_store.add("project-logo", "logo.png")
manifest.var_store.add("card", "n")
manifest.var_store.add("project-card", "card.png")
path_out = os.path.join(path, "output/html")
@@ -615,7 +630,7 @@ def test_html_render_plain():
def test_builder_plain():
"""A page that says nothing about itself beyond what it must."""
result = HtmlBuilder("pgBackRest", "Guide", None, None, None).render()
result = HtmlBuilder("pgBackRest", "Guide", None, None, None, None).render()
assert_not_in("og:image", result)
assert_not_in('name="description"', result)

View File

@@ -211,7 +211,7 @@ def test_lint_clean():
"src/x.auto.c.inc": b'#define ANY STRID5("any", 0x1)\n',
"src/x.vendor.c.inc": b'#define ANY STRID5("any", 0x1)\n',
# A binary file that is on the skip list, which is a deliberate and reviewable decision
"doc/resource/logo.png": b"\x00binary\n",
"doc/resource/card.png": b"\x00binary\n",
},
symlink=True,
)