Files
business-suite/tests/identity.py
T
Marc WäckerlinandClaude Opus 5 adc58e19d7 One LaTeX design for a company, in three templates
A company that writes with LaTeX ends up with a document class, a letter class
and a presentation theme that share a design and not a line of code. Every fix
is then made three times, two of them late and the third never.

Here the page is written once. A company declares its colours, faces and logo in
one file, and the document template, the letter class and the presentation theme
read that one file. Nothing in the suite carries a colour value, a font name or
a file name of any company, which is what lets it be published while the
companies stay private.

Every measure of the page follows from a measurement or from a named definition:
the room the head and the foot need is taken from the boxes they really build,
one line of the body text stands between the head and the text and between the
text and the foot, a heading never stands alone at the foot of a page, a picture
takes the size of the family, and a Markdown file reaches the same page as the
same document written in LaTeX.

The suite ships with a company that does not exist, Nordwind AG, so that it
builds and is measured anywhere: three colours, the TeX Gyre families every
installation carries, and an icon drawn in TikZ.

574 checks over five measurements, all of them on the rendered page or on the
build log rather than on the source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 15:06:07 +02:00

529 lines
23 KiB
Python

#!/usr/bin/env python3
"""The identity interface: the suite carries no company, and the company reaches it.
Two kinds of question, and both are needed. What the suite may NOT contain is a
property of its source, so it is measured there. What a company really reaches on
the page is a property of the page, so it is measured there.
"""
import os
import re
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import support
from support import Check, Page, build
EXAMPLES = os.path.join(support.ROOT, "examples")
SUITE_FILES = ("business-identity.sty", "business-suite.sty",
"business-brief.cls", "business-beamer.sty")
# A colour value in the suite is a company's property in a file every other company
# loads. The palette belongs to the identity of the company, the roles to the
# suite, and a role carries a name and never a number.
COLOUR_VALUE = re.compile(r"\\definecolor\s*\{[^}]*\}\s*\{(HTML|RGB|rgb|cmyk|gray)\}")
# The three templates take the identity from one place and carry none of their
# own: not a colour, not a face. A copy passes every check that asks whether
# the values are right, and is wrong on the day the one place changes.
TEMPLATES = ("business-suite.sty", "business-brief.cls", "business-beamer.sty")
OWN_VALUE = re.compile(r"^\s*(\\definecolor|\\colorlet|\\setmainfont"
r"|\\setsansfont|\\setmonofont|\\newfontfamily"
r"|\\renewcommand\*?\s*\\familydefault)", re.MULTILINE)
# Everything after \endinput is dead: LaTeX stops reading there. A block moved
# to the end of a file lands behind it without a word in any log, and every
# document is then built without what it does. Measured in a sister package on
# 2026-09-21, where the head height was measured by code that never ran.
ENDINPUT = re.compile(r"^\\endinput\s*$", re.MULTILINE)
DOCUMENT = r"""\documentclass[a4paper,10pt]{article}
\usepackage{nordwind}
\title{Lorem Ipsum}
\author{Erika Muster}
\date{21 September 2026}
\begin{document}
\maketitle
\section{Consetetur}
Dolor sit amet \success{} and \fail{} and an arrow →.
\end{document}
"""
# A company whose logo is a FILE and not a drawing, which is the other of the two
# ways the suite carries a logo and the one the README shows. `example-image` is
# the test image of TeX Live and lies on every installation.
FILE_COMPANY = r"""\ProvidesPackage{filecompany}[2026/09/21 A company with a logo file]
\RequirePackage{business-identity}
\businesscolours{brand = blue}
\setmainfont{Latin Modern Roman}
\businessfonts{default = rm, smallcaps = false}
\businessidentity{
name = Filecompany,
url = https://example.org,
address = Filecompany — https://example.org,
lockup = icon+text,
icon = example-image,
}
\DeclareOption*{\PassOptionsToPackage{\CurrentOption}{business-suite}}
\ProcessOptions\relax
\RequirePackage{business-suite}
\endinput
"""
FILE_DOCUMENT = r"""\documentclass[a4paper,10pt]{article}
\usepackage[nologo]{filecompany}
\begin{document}
\pagestyle{empty}
\noindent\logo[height=1cm]
\noindent\logo[height=2cm]
\end{document}
"""
LANGUAGE = r"""\documentclass[a4paper,10pt,ngerman]{article}
\usepackage{nordwind}
\usepackage[ngerman]{babel}
\title{Lorem Ipsum}
\author{Erika Muster}
\begin{document}
\maketitle
Dolor sit amet.
\end{document}
"""
# A company of two lines: the name, and the sentence it sets under the name, in
# a face and a colour of its own. Two pages, because the first carries the
# address line under the block and every other does not, and a logo in a
# sentence, because that one stays the one line it has always been.
TAGLINE_DOCUMENT = r"""\documentclass[a4paper,10pt]{article}
\usepackage{nordwind}
\businessidentity{tagline={Vernunft und Freiheit},
name face={\bfseries},
%s}
\title{Lorem Ipsum}
\author{Erika Muster}
\begin{document}
\maketitle
\section{Consetetur}
Lorem ipsum dolor sit amet. The logo in a sentence: \logo{} stays one line.
\newpage
\section{Dolor Sit}
At vero eos et accusam.
\end{document}
"""
TAGLINE_QUIET = TAGLINE_DOCUMENT % "tagline color=business-running-quiet"
# The same company without a word about the colour of its second line. Nothing
# is then set for that line, so it stands in the colour of the name over it.
TAGLINE_PLAIN = TAGLINE_DOCUMENT % "url=https://nordwind.example"
# The lockup alone on a page, so nothing but it carries ink. `nologo` keeps the
# head empty; what stands there is the one call being measured.
SIZES = r"""\documentclass[a4paper,10pt]{article}
\usepackage[nologo]{nordwind}
\businessidentity{tagline={Vernunft und Freiheit}}
\begin{document}
\thispagestyle{empty}
\noindent\logo[tagline, height=2cm%s]
\end{document}
"""
SIZES_MEASURED = SIZES % ""
SIZES_FACTOR = SIZES % ", taglinefactor=0.4"
SIZES_HIGH = SIZES % ", iconheight=3cm"
SIZES_WIDE = SIZES % ", iconwidth=1cm"
def spread(found, *words):
"""The left and the right edge of a group of words of one line."""
boxes = [word for word in found if word[0] in words]
if not boxes:
return None
return min(box[1] for box in boxes), max(box[3] for box in boxes)
def icon_box(page):
"""The rectangle the icon stands in: the leftmost group of ink columns."""
columns = page.ink_columns()
if not columns:
return None
end = columns[0]
for column in columns[1:]:
if column - end > 2:
break
end = column
rows = [y for y in range(page.height)
if any(page.dark(x, y) for x in range(columns[0], end + 1))]
factor = 72.0 / page.dpi
return ((end - columns[0] + 1) * factor, (rows[-1] - rows[0] + 1) * factor)
def lockup_sizes(check):
"""The two sizes of the lockup that measure themselves.
The tagline is set to the size at which it is exactly as WIDE as the name
over it, so the block has one left edge and one right edge. And the icon is
as HIGH as that block, because the mark stands beside the text and a height
of its own agrees with the text at one size of type and at no other. Both
are measured here, and both overrides with them: a factor for the tagline
and a height or a width for the icon, each one taking the measurement out of
the way for that call alone.
"""
source = support.write("lockupsizes.tex", SIZES_MEASURED)
pdf, _ = build(source, EXAMPLES)
found = support.words(pdf, 1)
name = spread(found, "Nordwind")
tag = spread(found, "Vernunft", "und", "Freiheit")
check.that(name and tag, "the lockup did not reach the page")
if not name or not tag:
return
wide, under = name[1] - name[0], tag[1] - tag[0]
check.that(abs(wide - under) < 1,
f"the name is {wide:.1f} points wide and the tagline "
f"{under:.1f}: the second line is set to the width of the first")
check.that(abs(name[0] - tag[0]) < 1,
"the two lines of the block do not share their left edge")
# The icon spans the block: from the top of the first line to the bottom of
# the last. Measured at the ink of the drawing, which fills its own box.
page = Page(pdf, 1)
box = icon_box(page)
check.that(box, "the icon did not reach the page")
if not box:
return
block = max(word[4] for word in found) - min(word[2] for word in found)
check.that(abs(box[1] - block) < 4,
f"the icon is {box[1]:.1f} points high and the text beside it "
f"{block:.1f}: the icon follows the text")
# And each override takes one of the two measurements out of the way.
source = support.write("lockupfactor.tex", SIZES_FACTOR)
pdf, _ = build(source, EXAMPLES)
found = support.words(pdf, 1)
name = spread(found, "Nordwind")
tag = spread(found, "Vernunft", "und", "Freiheit")
if name and tag:
check.that(tag[1] - tag[0] < name[1] - name[0] - 5,
"a company that names a factor for its tagline still got "
"the line set to the width of the name")
for name, fixture, index, asked in (("high", SIZES_HIGH, 1, 85.04),
("wide", SIZES_WIDE, 0, 28.35)):
source = support.write("lockup%s.tex" % name, fixture)
pdf, _ = build(source, EXAMPLES)
box = icon_box(Page(pdf, 1))
check.that(box, f"the icon of the {name} fixture did not reach the page")
if not box:
continue
# Three centimetres are 85.04 points and one is 28.35. The drawing fills
# its box, so a point and a half is the rasterizer.
check.that(abs(box[index] - asked) < 2,
f"the icon was asked for at {asked:.1f} points and came out "
f"at {box[index]:.1f}")
# The width follows the height in the proportions of the drawing: the icon
# of this company is a disc, so the two are the same number.
source = support.write("lockuphigh.tex", SIZES_HIGH)
pdf, _ = build(source, EXAMPLES)
box = icon_box(Page(pdf, 1))
if box:
check.that(abs(box[0] - box[1]) < 2,
f"the icon came out {box[0]:.1f} wide and {box[1]:.1f} high, "
"so the proportions of the drawing were not kept")
def source_checks(check):
for name in SUITE_FILES:
path = os.path.join(support.SUITE, name)
with open(path, encoding="utf-8") as handle:
text = handle.read()
found = COLOUR_VALUE.search(text)
check.that(found is None,
f"{name} carries a colour value: {found.group(0) if found else ''}")
ends = ENDINPUT.search(text)
if ends:
rest = text[ends.end():].strip()
check.that(rest == "",
f"{name} carries {len(rest.splitlines())} lines after "
"\\endinput, which LaTeX never reads")
if name in TEMPLATES:
own = OWN_VALUE.search(text)
check.that(own is None,
f"{name} sets a colour or a face of its own: "
f"{own.group(1) if own else ''} — it has to take both "
"from the identity it loads")
# Every role a template reads has to be declared, or the build fails with
# "undefined color" in a company that does not set it.
declared = set()
with open(os.path.join(support.SUITE, "business-identity.sty"),
encoding="utf-8") as handle:
identity = handle.read()
for name in re.findall(r"\\business@role\{([a-z-]+)\}", identity):
declared.add(name)
used = set()
for name in SUITE_FILES:
with open(os.path.join(support.SUITE, name), encoding="utf-8") as handle:
used |= set(re.findall(r"business-([a-z-]+)", handle.read()))
# The names of the packages themselves are no roles.
used -= {"identity", "suite", "brief", "beamer"}
missing = sorted(used - declared)
check.that(not missing, f"roles read but never declared: {missing}")
def page_checks(check):
source = support.write("identity-probe.tex", DOCUMENT)
pdf, _ = build(source, os.path.join(support.ROOT, "examples"))
page = Page(pdf, 1)
# The logo stands at the right edge of the type area, on the same edge as
# the text under it. Everything above the title belongs to the head.
whole = page.box()
check.that(whole is not None, "the page carries no ink at all")
left, top, right, bottom = whole
head = page.box(0, int(0.12 * page.height))
check.that(head is not None, "the head of the page is empty")
check.that(abs(head[2] - right) < 6,
f"the head does not end on the right edge of the text: "
f"{head[2]:.1f} against {right:.1f}")
# The page number stands at the foot, at the outer edge. Which rows the
# foot occupies is READ off the page and not guessed from a share of its
# height: the head and the foot follow the face of the company, and a share
# that holds for one company misses the foot of the next — measured when the
# example company changed its faces and the band that was searched came back
# empty.
rows = page.ink_rows()
last, current = [], []
for row in rows:
if current and row - current[-1] > 2:
last = current
current = []
current.append(row)
foot = page.box(current[0], current[-1] + 1) if current else None
check.that(foot is not None, "the foot carries no page number")
check.that(foot is not None and abs(foot[2] - right) < 6,
f"the page number is not at the right edge: "
f"{foot[2] if foot else 0:.1f} against {right:.1f}")
# What the PDF says about itself. A file whose properties say "untitled" is
# a file nobody finds again.
#
# It is read out of the XMP stream and not out of the info dictionary: that
# is where a file search, a reading program and an archive look today, and
# `hyperxmp` writes the author THERE and nowhere else — measured here, where
# `pdfinfo` prints a title and no author for a document that carries both.
xmp = support.run(["pdfinfo", "-meta", pdf]).stdout
check.that("<dc:title>" in xmp and "Lorem Ipsum" in xmp,
"the PDF carries no title")
check.that("<dc:creator>" in xmp and "Erika Muster" in xmp,
"the PDF carries no author")
# And the language, where the document says one. The template sets it
# nowhere: hyperref takes it from babel, and a second setting would only
# warn. That this really happens is measured and not assumed.
spoken = support.write("language-probe.tex", LANGUAGE)
speaking, _ = build(spoken, os.path.join(support.ROOT, "examples"))
said = support.run(["pdfinfo", "-meta", speaking]).stdout
check.that("dc:language" in said and "de" in said,
"a document that declares its language carries none in its XMP")
# The faces of the company, and nothing that LaTeX fell back to. Computer
# Modern is what arrives when a font has no shape in the encoding in force.
faces = support.fonts(pdf)
check.that(faces, "the PDF embeds no font at all")
fallbacks = [face for face in faces if face.startswith("CMR")
or face.startswith("CMSS") or face.startswith("SFRM")]
check.that(not fallbacks, f"LaTeX fell back to {fallbacks}")
# The marks come from the symbol family, because no text face carries them.
check.that(any("DejaVu" in face for face in faces),
f"the marks did not reach the page: {faces}")
def plain(check):
"""`plain` gives the identity and never the page of a document.
The two templates that ask for it are the letter class and the presentation
theme, and neither of them is an ordinary document: a page style and a
geometry laid over them wrecks their own layout. The case is measured with
the smallest document that can show it, because the failure is not a wrong
page — it is a run that stops: TeX skips a branch by counting `\\if` and
`\\fi` TOKENS, so a flag declared inside the skipped block closes the skip
with its own `\\fi` and the whole block runs. Reported on 2026-09-21 from a
fourth company, where the letter and the deck stopped building.
"""
source = os.path.join(support.ROOT, "tests", "documents", "plain.tex")
pdf, log = build(source, os.path.join(support.ROOT, "examples"))
check.that(os.path.exists(pdf), "a document with `plain` does not build")
with open(log, encoding="utf-8", errors="replace") as handle:
text = handle.read()
# Nothing of the page: no head, no foot, no geometry.
check.that("fancyhdr" not in text,
"`plain` loads fancyhdr, so it carries the page of a document")
check.that("geometry.sty" not in text,
"`plain` loads geometry, so it lays out the page again")
check.that("longtable" not in text,
"`plain` loads the table machinery of the document template")
def logo_from_file(check):
"""The other way a company carries its logo: a file instead of a drawing.
Two heights in one document, and the second asks for twice the first. A
height arrives as whatever the document writes — `1cm`, `2em`, `40pt` — and
a computation that reads the number and drops the unit passes at one of
them and fails at the other: measured in a sister package, where the gap
under the logo collapsed at every height given in centimetres while the
same formula looked right in em.
"""
support.write("filecompany.sty", FILE_COMPANY)
source = support.write("logofile.tex", FILE_DOCUMENT)
pdf, _ = build(source, support.BUILD)
page = Page(pdf, 1)
rows = page.ink_rows()
check.that(rows, "the logo file did not reach the page at all")
if not rows:
return
blocks, current = [], []
for row in rows:
if current and row - current[-1] > 2:
blocks.append(current)
current = []
current.append(row)
blocks.append(current)
check.that(len(blocks) == 2,
f"the page should carry two logos, it carries {len(blocks)}")
if len(blocks) != 2:
return
small = blocks[0][-1] - blocks[0][0] + 1
large = blocks[1][-1] - blocks[1][0] + 1
ratio = large / small
check.that(1.9 < ratio < 2.1,
f"the logo asked for at twice the height came out {ratio:.2f} "
"times as high")
def tagline(check):
"""The lockup of two lines: the name, and the company's sentence under it.
Four things are measured, and each of them is a decision the family took.
The head of the first page carries three lines — name, tagline, address —
and the head of the second two, because the address belongs on the sheet
that leaves the company and the tagline belongs to the mark. The logo in a
SENTENCE stays one line, because a place asks for the tagline and a company
only says what it is; without that division a mark in running text would
open the line it stands in. And the page reserves the room for the taller
block: the head is measured when the identity arrives, and a template that
measured it before the company declared itself asks fancyhdr for the
difference on every page — 16.8 points, measured on 2026-09-22.
"""
source = support.write("taglinedoc.tex", TAGLINE_QUIET)
pdf, log = build(source, EXAMPLES)
with open(log, encoding="utf-8") as handle:
printed = handle.read()
check.that("headheight is too small" not in printed,
"the page kept less room for the head than the two-line lockup "
"takes, so fancyhdr asked for the difference")
for number, carries in ((1, True), (2, False)):
found = support.words(pdf, number)
head = [word for word in found if word[4] < 90]
spoken = " ".join(word[0] for word in head)
check.that("Vernunft" in spoken,
f"the tagline is not in the head of page {number}: "
f"{spoken}")
check.that(("nordwind.example" in spoken) == carries,
f"the address line is {'missing from' if carries else 'in'} "
f"the head of page {number}, and it belongs on the first "
"page alone")
name = [word for word in head if word[0] == "Nordwind"]
tag = [word for word in head if word[0] == "Vernunft"]
if not name or not tag:
continue
name, tag = name[0], tag[0]
# The two lines stand flush left with each other, and the second is the
# smaller: the family sets the tagline at a share of the name.
check.that(abs(name[1] - tag[1]) < 1,
f"the name begins at {name[1]:.1f} points and the tagline "
f"at {tag[1]:.1f}: the two lines of the block share their "
"left edge")
check.that(tag[2] > name[4] - 1,
"the tagline does not stand under the name")
high, low = name[4] - name[2], tag[4] - tag[2]
check.that(low < high,
f"the tagline is {low:.1f} points high and the name "
f"{high:.1f}: the second line of the lockup is the smaller "
"one")
# A logo in a SENTENCE carries no tagline: the name stands in the line of
# the body text, and the tagline of the company is nowhere near it.
body = [word for word in support.words(pdf, 1) if word[2] > 200]
inline = [word for word in body if word[0] == "Nordwind"]
check.that(inline, "the logo in the sentence did not reach the page")
if inline:
near = [word for word in body
if word[0] == "Vernunft"
and abs(word[2] - inline[0][2]) < 30]
check.that(not near,
"the logo in a sentence carries the tagline, so a mark in "
"running text opens the line it stands in")
def darkest(page, word):
"""The darkest pixel inside the box a word stands in, 0 to 255.
The darkest pixel is the core of a stem, and that is the colour the type is
set in: the mean over a word measures how much white stands between its
letters, which is a property of the letters and of the size, not of the
colour.
"""
scale = page.dpi / 72.0
left, top = int(word[1] * scale), int(word[2] * scale)
right, low = int(word[3] * scale) + 1, int(word[4] * scale) + 1
return min(page.pixels[y * page.width + x]
for y in range(max(top, 0), min(low, page.height))
for x in range(max(left, 0), min(right, page.width)))
def tagline_colour(check):
"""A company that names no colour for its second line sets none.
The tagline then stands in the colour the name over it stands in, because
the lockup sets its colour once and no line of the block sets one of its
own. Named, the colour reaches that line alone and the name keeps the one
of the lockup. Measured at the darkest pixel of each word: the quiet role
of the example company is a 55 percent tint of the brand colour, so the two
lines come out far apart where it is named and together where it is not.
"""
tone = {}
for name, fixture in (("quiet", TAGLINE_QUIET), ("plain", TAGLINE_PLAIN)):
source = support.write("tagcolour%s.tex" % name, fixture)
pdf, _ = build(source, EXAMPLES)
page = Page(pdf, 2)
found = {word[0]: word for word in support.words(pdf, 2)
if word[4] < 90}
check.that("Nordwind" in found and "Vernunft" in found,
f"the head of the {name} company carries no lockup")
if "Nordwind" not in found or "Vernunft" not in found:
return
tone[name] = (darkest(page, found["Nordwind"]),
darkest(page, found["Vernunft"]))
check.that(abs(tone["plain"][0] - tone["plain"][1]) < 12,
f"without a colour of its own the tagline comes out at "
f"{tone['plain'][1]} against the {tone['plain'][0]} of the name: "
"a colour was set for that line")
check.that(tone["quiet"][1] - tone["quiet"][0] > 30,
f"the named colour reached the tagline at {tone['quiet'][1]} "
f"against the {tone['quiet'][0]} of the name, and the quiet role "
"of the company is far lighter than its brand colour")
check.that(abs(tone["quiet"][0] - tone["plain"][0]) < 12,
"a colour named for the tagline changed the colour of the name")
def main():
check = Check("identity")
source_checks(check)
plain(check)
page_checks(check)
logo_from_file(check)
tagline(check)
tagline_colour(check)
lockup_sizes(check)
return check.done()
if __name__ == "__main__":
sys.exit(main())