Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,13 @@ because it turns other people's test suites red.
rest without a word, and the set still expected such a workbook to be
accepted, certain. Past that width `sheet.xlsx` is now `unspecified` for
`count_limit`, like the wide CSV beside it. Up to it, nothing changes.
- **A picture too low for its label no longer says it carries one.** The
label is drawn as large as the width allows, so a low picture cuts it off,
and a BMP 512 px wide and 1 px tall said `label_embedded: true` with no
text in it at all. Every image format and both film formats now say
`false` with a `label_omitted` note when the text is not whole in the
picture. The pixels are the same as before.
- **A picture whose label cannot be read no longer says it carries one.**
The label is drawn as large as the width allows, so a low picture cuts it
off, and a BMP 512 px wide and 1 px tall said `label_embedded: true` with
no text in it. A low, wide film of one second drew its moving square over
the label. Every image format and both film formats now say `false` with a
`label_omitted` note when no picture shows the whole text. The pixels are
the same as before.
- **`tfg cleanup --yes` keeps a file that changed after it was checked.**
Cleanup checks every file before it removes any, and a file changed in that
time was removed without `--force`. It now looks again just before it
Expand Down
49 changes: 49 additions & 0 deletions internal/format/imagelabel/imagelabel.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,55 @@ func CellsOf(width, chars int) Cells {
return Cells{First: pad, Step: (glyphWidth + 1) * s, Width: glyphWidth * s}
}

// TextRect is where Draw puts the text of a label of chars characters across
// width, in a picture tall enough to hold it: from the left edge of the first
// character to the right edge of the last, over the rows of the glyphs - the
// band's padding is not in it. The empty rectangle when Draw would draw
// nothing. A film asks it to know whether the square it draws over the label
// leaves the text whole (Hides).
func TextRect(width, chars int) image.Rectangle {
c := CellsOf(width, chars)
if c.Width == 0 {
return image.Rectangle{}
}
return image.Rect(c.First, pad, c.First+(chars-1)*c.Step+c.Width, textHeight(width, chars))
}

// Hides says whether r, painted in the label's ink over the text Draw puts
// across width, changes a pixel of that text - a pixel of the background
// inside it, between the glyphs or within them. Ink over the glyphs' own ink
// changes nothing, and the band around the text is not the text, the rule
// Readable keeps for the padding at the foot: a square over the last column
// of a 7 and the band beside it leaves the label as it was (measured on the
// pixels, 2026-11-03).
func Hides(width int, text string, r image.Rectangle) bool {
t := laidText{Cells: CellsOf(width, len(text)), scale: scaleFor(width, len(text)), runes: []rune(text)}
in := r.Intersect(TextRect(width, len(text)))
// Draw spaces the text by its bytes and draws its runes, so no glyph
// stands past the last rune.
in.Max.X = min(in.Max.X, t.First+(len(t.runes)-1)*t.Step+t.Width)
for i := range max(in.Dx(), 0) * in.Dy() {
if !t.ink(in.Min.X+i%in.Dx(), in.Min.Y+i/in.Dx()) {
return true
}
}
return false
}

// laidText is a text where Draw lays it out, asked about its pixels.
type laidText struct {
Cells
scale int
runes []rune
}

// ink says whether the pixel at x, y inside the text is the ink of a glyph,
// rather than the background between the glyphs or within one.
func (t laidText) ink(x, y int) bool {
k, off := (x-t.First)/t.Step, (x-t.First)%t.Step
return off < t.Width && glyph(t.runes[k])[(y-pad)/t.scale*glyphWidth+off/t.scale] == '#'
}

// BandHeight is how tall the band Draw paints for a label of chars characters
// across width is, before it is cut to the picture - 0 when the label does not
// fit. A film draws a second line under the first and asks this before it
Expand Down
29 changes: 22 additions & 7 deletions internal/format/video/choose.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,30 @@ type Choice struct {
Named bool
}

// Labelled is whether this picture carries its label where it can be read:
// drawn across its width, and whole in its height. The manifest says this,
// and the drawing asks the package's Labelled, which is the width alone - a
// low film draws its label and cuts it off (R14 of
// docs/STABILITY-REVIEW-2026-10-26.md, where the pixels stayed as they were).
func (c Choice) Labelled() bool {
// Labelled is whether this film carries its label where it can be read:
// drawn across its width, whole in its height, and whole in at least one of
// its pictures under the square. The manifest says this, and the drawing asks
// the package's Labelled, which is the width alone - a low film draws its
// label and cuts it off (R14 of docs/STABILITY-REVIEW-2026-10-26.md), and a
// low wide one of few changes draws the square over it (O288). The pixels
// stayed as they were both times.
func (c Choice) Labelled(t Timeline) bool {
return c.readable() && !c.squareOverLabel(t)
}

// readable is whether the label is drawn and whole in the picture's height,
// before the square is drawn over it.
func (c Choice) readable() bool {
return Labelled(c.Width, c.Label) && imagelabel.Readable(c.Width, c.Height, len(c.Label))
}

// squareOverLabel is whether the square crosses the text of a label drawn
// whole in every picture of the film - the one case the plan owes a note of
// its own for.
func (c Choice) squareOverLabel(t Timeline) bool {
return c.readable() && !geometryOf(c.Width, c.Height, c.Label, t).labelWholeIn(t.Changes(), c.Label)
}

// ClockShown is whether this film's pictures show the clock.
func (c Choice) ClockShown(t Timeline) bool { return ClockShown(c.Width, c.Height, c.Label, t) }

Expand Down Expand Up @@ -292,6 +307,6 @@ func Facts(c Choice, s Settings) map[string]any {
"quality": s.Quality,
"compression": "av1",
"audio": false,
format.PropertyLabelEmbedded: c.Labelled(),
format.PropertyLabelEmbedded: c.Labelled(s.Timeline),
}
}
26 changes: 24 additions & 2 deletions internal/format/video/picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,28 @@ func (g geometry) squareX(c int64) int {
return (g.width - g.side) * int(c%squareSteps) / (squareSteps - 1)
}

// squareAt is the square standing at column x - what a picture paints, and
// what the label is asked about, so the two cannot come apart.
func (g geometry) squareAt(x int) image.Rectangle {
return image.Rect(x, g.squareY, x+g.side, g.squareY+g.side)
}

// labelWholeIn says whether the text of the label is whole in at least one
// picture of a film of this many changes - the square, drawn over it in its
// ink, changing none of it there. It stands in squareSteps places, so a film
// of that many changes or more stands in all of them, and a film of one change
// only at the left edge, over the start of the label of a low picture (O288 of
// docs/OBSERVATIONS.md). One whole picture is enough, by the owner's decision
// of 2026-10-27: it is the one that identifies the file.
func (g geometry) labelWholeIn(changes int64, label string) bool {
for c := range min(changes, squareSteps) {
if !imagelabel.Hides(g.width, label, g.squareAt(g.squareX(c))) {
return true
}
}
return false
}

// clockRight is the column after the clock's last character, nought without
// a clock.
func (g geometry) clockRight() int {
Expand Down Expand Up @@ -350,7 +372,7 @@ func (f *film) whole(l look) Planes {
b := work.Rect
imagelabel.Draw(work.SubImage(image.Rect(0, f.clockFrom, b.Dx(), b.Dy())).(*image.RGBA), l.clock)
}
draw.Draw(work, image.Rect(l.x, f.squareY, l.x+f.side, f.squareY+f.side), f.square, image.Point{}, draw.Src)
draw.Draw(work, f.squareAt(l.x), f.square, image.Point{}, draw.Src)
planes, _ := newPlanes(f.width, f.height)
toPlanes(work, planes)
return planes
Expand Down Expand Up @@ -419,7 +441,7 @@ func (p *painter) drawIn(l look, r image.Rectangle) Planes {
if f.showClock() && s.Rect.Min.Y <= f.clockFrom && f.clockFrom < s.Rect.Max.Y {
imagelabel.Draw(s.SubImage(image.Rect(0, f.clockFrom, f.width, s.Rect.Max.Y)).(*image.RGBA), l.clock)
}
draw.Draw(s, image.Rect(l.x, f.squareY, l.x+f.side, f.squareY+f.side), f.square, image.Point{}, draw.Src)
draw.Draw(s, f.squareAt(l.x), f.square, image.Point{}, draw.Src)
convertRect(s, out, r.Min, in)
}
return out
Expand Down
7 changes: 7 additions & 0 deletions internal/format/video/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ func Plan(formatID string, r format.Request, need func(Stream) int64, refuse fun
// The answer is c.Labelled, which Facts has already put in the plan. This
// is the note a label drawn and cut off at the foot of the picture owes.
_, p.Notes = imagelabel.Claim(r.Label && Labelled(c.Width, c.Label), c.Width, c.Height, len(c.Label), p.Notes)
if c.squareOverLabel(s.Timeline) {
p.Notes = append(p.Notes, format.Note{
Code: "label_omitted",
Detail: core.Says("video.TheSquareCoversTheLabel", "The picture is %dx%d and the square that steps across it covers part of the label in every picture, so this file carries no readable label. Its name and the manifest still identify it.",
core.A("Width", c.Width), core.A("Height", c.Height)),
})
}
if !c.ClockShown(s.Timeline) {
p.Notes = append(p.Notes, format.Note{
Code: "clock_omitted",
Expand Down
172 changes: 172 additions & 0 deletions internal/guard/labelclaim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/donislawdev/TestingFilesGenerator/internal/core"
"github.com/donislawdev/TestingFilesGenerator/internal/format"
"github.com/donislawdev/TestingFilesGenerator/internal/format/imagelabel"
"github.com/donislawdev/TestingFilesGenerator/internal/format/video"
)

// A picture says it carries its label only when the text of the label is whole
Expand Down Expand Up @@ -102,6 +103,177 @@ func TestALabelIsReadableFromTheFirstHeightThatHoldsItsText(t *testing.T) {
}
}

// A film says it carries its label only when one of its pictures shows the
// text whole under the square.
//
// The square steps across a film in ten places and is drawn over the label in
// the label's own ink. On a low wide film it rides across the text, and until
// 2026-11-03 a film of 1920x64 and one change drew it over "tfg -" in its only
// picture and said label_embedded true (O288 of docs/OBSERVATIONS.md). Asked
// of the pixels: the text rows of every picture are compared with the same
// picture of the film made tall, where the square passes under the label, over
// the columns the label's ink covers - measured from the label drawn on its
// own. Across heights at three scales and widths around the one where the
// second of two pictures first clears the text, so the answer is held at both
// edges it can move at, and each series has to give both answers.
func TestAFilmSaysItCarriesItsLabelOnlyWhenAPictureShowsItWhole(t *testing.T) {
const seed, tall = 7, 256
label := core.Label("webm", 1<<20, seed)
across := len(label)*4 + 4 // the width that draws it at scale 1, as in the guard above
timeline := func(changes int64) video.Timeline {
tl, err := video.NewTimeline("webm", changes*1000, 60_000, 1000, 30)
if err != nil {
t.Fatal(err)
}
return tl
}
// shown says whether picture c of the film leaves the label's ink as the
// tall film shows it, over every row and column the ink reaches. The tall
// picture is the same at every height, so it is painted once.
talls := map[[2]int64]video.Planes{}
shown := func(w, h int, tl video.Timeline, ink image.Rectangle, c int64) bool {
got := video.WholePicture(w, h, seed, label, tl, c)
key := [2]int64{int64(w)*1000 + tl.Changes(), c}
want, ok := talls[key]
if !ok {
want = video.WholePicture(w, tall, seed, label, tl, c)
talls[key] = want
}
for y := ink.Min.Y; y < ink.Max.Y; y++ {
for x := ink.Min.X; x < ink.Max.X; x++ {
if got.Y[y*w+x] != want.Y[y*w+x] {
return false
}
}
}
return true
}
ask := func(w, h int, tl video.Timeline) (said, seen bool, compared bool) {
said = video.Choice{Width: w, Height: h, Seed: seed, Label: label}.Labelled(tl)
if !video.Labelled(w, label) || !imagelabel.Readable(w, h, len(label)) {
return said, false, false
}
ink := labelInkBounds(w, label)
if text := imagelabel.TextRect(w, len(label)); ink != text {
t.Fatalf("at %d px across the label's ink covers %v and TextRect says %v - this guard reads the ink and the film asks TextRect, so the two have to agree on its label", w, ink, text)
}
for c := range tl.Changes() {
if shown(w, h, tl, ink, c) {
return said, true, true
}
}
return said, false, true
}
type series struct {
name string
cases [][2]int
tl video.Timeline
}
var all []series
for _, changes := range []int64{1, 2, 10} {
for _, scale := range []int{1, 2, 4} {
s := series{name: "scale " + strconv.Itoa(scale) + ", " + strconv.FormatInt(changes, 10) + " change(s), heights", tl: timeline(changes)}
for h := 1; h <= 120; h++ {
s.cases = append(s.cases, [2]int{across * scale, h})
}
all = append(all, s)
}
}
// The square of the second of two pictures stands a ninth of the way along
// the room it leaves, so at 64 px tall, where it is 64 px and rides over
// the text, a film 9 x + 64 px wide has it at column x. Walked over the
// last two characters and past the text's right edge, which is where it
// stops hiding anything - not at the edge, because the last column of a 7
// is ink all the way down.
right := imagelabel.TextRect(across*4, len(label)).Max.X
step := imagelabel.CellsOf(across*4, len(label)).Step
edge := series{name: "2 changes at 64 px, widths", tl: timeline(2)}
for x := right - 2*step; x <= right+2; x++ {
edge.cases = append(edge.cases, [2]int{9*x + 64, 64})
}
all = append(all, edge)

for _, s := range all {
answers := map[bool]int{}
for _, wh := range s.cases {
said, seen, compared := ask(wh[0], wh[1], s.tl)
if !compared {
if said {
t.Errorf("%s: a film of %dx%d says it carries its label, and the label is not whole in its height", s.name, wh[0], wh[1])
}
continue
}
answers[seen]++
if said != seen {
t.Errorf("%s: a film of %dx%d says label_embedded %v, and a picture of it shows the label whole: %v", s.name, wh[0], wh[1], said, seen)
}
}
if answers[true] == 0 || answers[false] == 0 {
t.Errorf("%s: the pictures showed the label whole %d time(s) and covered %d - the series never reached the edge it is there for", s.name, answers[true], answers[false])
}
}
}

// And the plan of both film formats says it, with a note when the square is
// what hides the label: the film of O288, and the same film at the default
// ten changes, whose last picture shows the label whole.
func TestAFilmWhoseSquareCoversItsLabelSaysSoInItsPlan(t *testing.T) {
asked := 0
for _, id := range format.IDs() {
d, err := format.Get(id)
if err != nil {
t.Fatal(err)
}
if _, ok := settingNamed(d, video.SettingChangeInterval); !ok {
continue
}
asked++
for _, c := range []struct {
duration string
want bool
}{{"1s", false}, {"10s", true}} {
props := map[string]string{"width": "1920", "height": "64", video.SettingDuration: c.duration}
r := format.Request{Seed: 1, Label: true, Properties: props}
r.Bytes = format.SmallestRemembered(d, r) + 64<<10
p, err := d.Generator.Plan(r)
if err != nil {
t.Fatalf("%s 1920x64 for %s: %v", id, c.duration, err)
}
embedded, _ := p.Properties[format.PropertyLabelEmbedded].(bool)
omitted := 0
for _, n := range p.Notes {
if n.Code == "label_omitted" {
omitted++
}
}
if embedded != c.want || omitted != map[bool]int{true: 0, false: 1}[c.want] {
t.Errorf("%s 1920x64 for %s says label_embedded %v with %d label_omitted note(s), want %v with %d - notes %v",
id, c.duration, embedded, omitted, c.want, map[bool]int{true: 0, false: 1}[c.want], p.Notes)
}
}
}
if asked < 2 {
t.Fatalf("only %d format(s) declare %s - the registry was not read", asked, video.SettingChangeInterval)
}
}

// labelInkBounds is the rectangle the pixels of a label drawn alone across
// width cover, read from the pixels.
func labelInkBounds(width int, label string) image.Rectangle {
img := image.NewRGBA(image.Rect(0, 0, width, 64))
imagelabel.Draw(img, label)
band := img.RGBAAt(0, 0)
var ink image.Rectangle
for y := range 64 {
for x := range width {
if c := img.RGBAAt(x, y); c != band && c.A != 0 {
ink = ink.Union(image.Rect(x, y, x+1, y+1))
}
}
}
return ink
}

func hasNote(notes []format.Note, code string) bool {
for _, n := range notes {
if n.Code == code {
Expand Down
Loading
Loading