diff --git a/.github/window.png b/.github/window.png index 6f39731..6a7fa02 100644 Binary files a/.github/window.png and b/.github/window.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d387e..eadfc3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,15 @@ because it turns other people's test suites red. ## [Unreleased] +### Added + +- **TIFF, the twenty first format.** Uncompressed, RGB, one page, little-endian. + `tfg generate --format tiff --size 300kb` writes a picture worth 300 kB rather + than a thumbnail followed by filler, because TIFF stores its pixels + uncompressed and the size is arithmetic - the same shape as BMP. `width` and + `height` can be set, and naming one lets the other be worked out from the + size. The smallest TIFF this produces is 183 B. + ## [0.2.0] - 2026-08-28 ### Breaking diff --git a/README.md b/README.md index ab8a881..2bd6e66 100644 --- a/README.md +++ b/README.md @@ -23,18 +23,18 @@ reference is below it. ## 📁 Formats it generates -Twenty, and every one is a **real file of that format** - it opens in the +Twenty one, and every one is a **real file of that format** - it opens in the software that owns it, at the exact size you asked for: | group | formats | |---|---| | 📄 **Documents** | `pdf`, `docx` (Word), `xlsx` (Excel), `pptx` (PowerPoint) | -| 🖼️ **Images** | `png`, `jpg`, `bmp`, `gif`, `ico`, `svg` | +| 🖼️ **Images** | `png`, `jpg`, `bmp`, `gif`, `ico`, `svg`, `tiff` | | 📝 **Text and markup** | `txt`, `md`, `csv`, `json`, `xml`, `html`, `log` | | 🗜️ **Archives** | `zip`, `targz` (`.tar.gz`) | | 🔊 **Audio** | `wav` | -Coming next: `7z`, `tiff`, `webp`, `mp3`, `mp4`. +Coming next: `7z`, `webp`, `mp3`, `mp4`. Most of them take settings of their own - image dimensions, JPEG quality, PDF page count, rows and columns in a spreadsheet, what goes inside an archive. See @@ -122,8 +122,9 @@ false failures, and a suite that cries wolf gets switched off. **Download a binary.** Take the archive for your system from the [releases page](https://github.com/donislawdev/TestingFilesGenerator/releases), unpack it and run it. `tfg` is the command line, `tfg-gui` is the desktop -window. They are not signed yet, so your system will warn you the first time - -the release notes say exactly what to expect and why. +window. The Windows and macOS downloads are signed, so they start without a +warning about an unknown developer. The Linux ones are not, because desktop +Linux has no equivalent to sign them with. **With Go installed:** @@ -396,7 +397,7 @@ ignored quietly: `extends`, `with`, `policy`, `engine`, `defaults.fill`, ## 📁 Formats in detail -The twenty formats are listed near the top of this file. Each is produced at an +The twenty one formats are listed near the top of this file. Each is produced at an exact size and checked against independent readers before it ships - a PNG is opened and its pixels compared, a DOCX is read back by three separate libraries, an archive is extracted. @@ -414,7 +415,7 @@ recipe. `tfg formats ` prints the allowed range or list for each: | format | settings | |---|---| | `pdf` | `pages`, `page_size` | -| `png`, `bmp`, `gif` | `width`, `height` | +| `png`, `bmp`, `gif`, `tiff` | `width`, `height` | | `jpg` | `width`, `height`, `quality` | | `ico` | `width`, `height`, `embed` | | `wav` | `sample_rate`, `bit_depth`, `channels`, `content` | @@ -624,13 +625,13 @@ a valid one of its format. Honest scope, because a tool that oversells itself wastes your afternoon. -**Working end to end:** twenty formats, recipes, presets, the desktop window, +**Working end to end:** twenty one formats, recipes, presets, the desktop window, `generate`, `validate`, `verify`, `cleanup`, boundary sets, archive contents, size ranges, per format settings, manifests and every exit code above. -**Not there yet:** five more formats. The preset catalogue has one entry so far. -The recipe keys listed under [Not built yet](#not-built-yet). Binaries are not -signed. +**Not there yet:** four more formats. The preset catalogue has one entry so far. +The recipe keys listed under [Not built yet](#not-built-yet). The Linux +downloads are unsigned. Found a problem or want a format? The [issue tracker](https://github.com/donislawdev/TestingFilesGenerator/issues) is diff --git a/internal/format/all/all.go b/internal/format/all/all.go index 543629e..029b60e 100644 --- a/internal/format/all/all.go +++ b/internal/format/all/all.go @@ -23,6 +23,7 @@ import ( _ "github.com/donislawdev/TestingFilesGenerator/internal/format/pptx" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/svgfile" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/targz" + _ "github.com/donislawdev/TestingFilesGenerator/internal/format/tiff" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/txt" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/wav" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/xlsx" diff --git a/internal/format/format.go b/internal/format/format.go index 6b11810..9d3d0dd 100644 --- a/internal/format/format.go +++ b/internal/format/format.go @@ -44,9 +44,14 @@ const ( // Placement says where in the stream a format tolerates arbitrary bytes. // // This is not decoration. Four Tier 1 formats pad at the front - MP3 inside -// its ID3v2 tag, BMP and ICO in the gap before the image data, TIFF between -// its directories. An interface built around "padding goes at the end" would -// have to be rewritten when the twelfth format arrives. +// its ID3v2 tag, and BMP, ICO and TIFF in the gap before the image data. An +// interface built around "padding goes at the end" would have to be rewritten +// when the twelfth format arrives. +// +// TIFF used to be described here as padding "between its directories", which +// is what the documents said before anybody measured. It writes one directory, +// so there is no between - and the gap it does use is the one StripOffsets +// points past, the same shape as bfOffBits in BMP. Corrected 2026-08-29. type Placement string const ( diff --git a/internal/format/tiff/tiff.go b/internal/format/tiff/tiff.go new file mode 100644 index 0000000..f36f98a --- /dev/null +++ b/internal/format/tiff/tiff.go @@ -0,0 +1,568 @@ +// Package tiff generates uncompressed TIFF images. +// +// The second format whose size is arithmetic rather than whatever an encoder +// decides, after BMP - and it is built the same way for the same reason. The +// pixels are stored uncompressed, so a request for 10 MB can be answered with +// a picture worth 10 MB instead of a thumbnail followed by filler nobody can +// see. +// +// Written by hand rather than taken from a library, and that was a decision +// with a measurement behind it (docs/STACK.md section 4.9). The candidate, +// x/image/tiff, encodes two of the five compressions its own enum declares, +// gates Predictor on the one it cannot do, writes little-endian only and emits +// a single directory - two of the four variant axes this project documents. It +// would also have been the first outside encoder inside the byte stability +// contract D11, and its version is raised by the window toolkit rather than by +// us, so updating Fyne would have moved TIFF hashes in the command line binary. +// +// The padding channel is the gap between the header and the pixel data, which +// StripOffsets points past. That is the one candidate of five the format +// itself talks about: everything before that offset is space the file +// describes rather than space it keeps quiet about - the same shape as +// bfOffBits in BMP. Measured on five independent readers at every size from +// 1 B to 10 MiB (docs/MVP-FORMATS.md section 2.11). +package tiff + +import ( + "context" + "encoding/binary" + "fmt" + "image" + "image/color" + "io" + "strconv" + + "github.com/donislawdev/TestingFilesGenerator/internal/core" + "github.com/donislawdev/TestingFilesGenerator/internal/format" + "github.com/donislawdev/TestingFilesGenerator/internal/format/imagelabel" +) + +const ( + generatorVersion = "1" + + // header is the eight byte TIFF header: the byte order mark, the magic + // number 42 and the offset of the first directory. + header = 8 + + // samplesPerPixel is three, one byte each for red, green and blue. No + // alpha and no palette, which is the shape every reader understands. + samplesPerPixel = 3 + bitsPerSample = 8 + + // entryCount is how many directory entries every file we write carries. + // Fixed, because the picture never changes which tags it needs. + entryCount = 12 + // directory is the whole IFD: the entry count, the entries, and the + // offset of the next directory, which is always zero because we write one. + directory = 2 + 12*entryCount + 4 + // heap is the space after the directory for values too long for the four + // bytes an entry holds: BitsPerSample is three shorts, and the two + // resolution values are rationals of eight bytes each. + heap = samplesPerPixel*2 + 8 + 8 + + // overhead is everything in the file that is not a pixel and not padding. + overhead = header + directory + heap + + minDimension = 1 + maxDimension = 20000 + + // StripOffsets and StripByteCounts are LONG, which is a four byte + // unsigned field, so neither the offset of the pixels nor their length + // can pass 4 GiB. Reasoned from the format, not measured - a file that + // size will not fit on the machine this was written on. + maxFileBytes = 1<<32 - 1 + + // The tallest label band the rasteriser produces, so the strip carrying + // the label is built once at a known height and the rest of the picture + // streams past without ever being held. + maxBandHeight = 24 +) + +// TIFF data types, from the specification. +const ( + typeShort = 3 + typeLong = 4 + typeRational = 5 +) + +// Tags this generator writes, in the ascending order a directory requires. +const ( + tagImageWidth = 256 + tagImageLength = 257 + tagBitsPerSample = 258 + tagCompression = 259 + tagPhotometric = 262 + tagStripOffsets = 273 + tagSamplesPerPixel = 277 + tagRowsPerStrip = 278 + tagStripByteCounts = 279 + tagXResolution = 282 + tagYResolution = 283 + tagResolutionUnit = 296 +) + +func init() { + format.Register(format.Descriptor{ + ID: "tiff", + Extension: ".tiff", + Fidelity: format.FidelityFull, + Determinism: format.DeterminismByte, + + // The smallest TIFF this generator can produce: one pixel of three + // bytes, plus everything that is not a pixel. + MinBytes: overhead + samplesPerPixel, + + Padding: format.PaddingChannel{ + // Measured against five independent readers - Pillow, the Windows + // Imaging Component, GDI+, exiftool and x/image - at every size + // from one byte to 10 MiB, odd sizes included. All five read the + // image and return identical pixels. + // + // Five channels passed that measurement and this is the one the + // format itself sets aside: StripOffsets says where the pixels + // begin, so anything between the header and that offset is space + // the file describes. Bytes after the directory are accepted by + // all five too, but no field mentions them. + Name: "the gap between the header and the pixel data", + Where: format.PlacementInside, + Capacity: maxFileBytes, + }, + Label: format.LabelVisible, + Oracle: "pillow", + Properties: []format.Property{ + { + Name: "width", Kind: format.PropertyInt, + Min: minDimension, Max: maxDimension, Unit: "pixels", + Detail: "How wide the picture is. Left out, the picture is sized to fill the bytes you asked for.", + }, + { + Name: "height", Kind: format.PropertyInt, + Min: minDimension, Max: maxDimension, Unit: "pixels", + Detail: "How tall the picture is. Left out, the picture is sized to fill the bytes you asked for.", + }, + }, + GeneratorVersion: generatorVersion, + Generator: generator{}, + }) +} + +type generator struct{} + +type memo struct { + width, height int + seed uint64 + label string + // gap is how many bytes sit between the header and the pixel data. + gap int64 +} + +// pixelBytes is the length of the image data. A TIFF row is not rounded up to +// anything, which is the one place this format is simpler than BMP. +func pixelBytes(width, height int) int64 { + return int64(width) * int64(height) * samplesPerPixel +} + +func (generator) Plan(r format.Request) (format.Plan, error) { + label := "" + if r.Label { + label = core.Label("tiff", r.Bytes, r.Seed) + } + + w, h, err := chooseSize(r) + if err != nil { + return format.Plan{}, err + } + + bare := overhead + pixelBytes(w, h) + if r.Bytes < bare { + return format.Plan{}, &format.BelowMinimumError{ + Format: "TIFF", + Requested: r.Bytes, + Minimum: bare, + Reason: fmt.Sprintf( + "a %dx%d picture is %d B of pixels at three bytes each, and the header, the directory and its values take another %d B", + w, h, pixelBytes(w, h), overhead), + Hint: fmt.Sprintf("Ask for %d B or more, or set a smaller width and height", bare), + } + } + if r.Bytes > maxFileBytes { + return format.Plan{}, &format.AboveMaximumError{ + Format: "TIFF", + Requested: r.Bytes, + Maximum: maxFileBytes, + Reason: "a TIFF locates its pixels with a four byte offset, so the format cannot describe a file this large", + Hint: "Ask for 4 GiB or less, or pick a format with no offset of its own such as gif.", + } + } + + m := memo{width: w, height: h, seed: r.Seed, label: label, gap: r.Bytes - bare} + + p := format.Plan{ + Bytes: r.Bytes, + Exact: true, + Determinism: format.DeterminismByte, + Properties: map[string]any{ + "width": w, + "height": h, + "bit_depth": bitsPerSample * samplesPerPixel, + "compression": "none", + "byte_order": "little-endian", + "row_order": "top-down", + }, + } + + labelled := r.Label && imagelabel.Fits(w, len(label)) + if r.Label && !labelled { + p.Notes = append(p.Notes, format.Note{ + Code: "label_omitted", + Detail: fmt.Sprintf( + "The picture is %d px wide and the label needs more room, so this file carries no visible label. Its name and the manifest still identify it.", + w), + }) + } + p.Properties[format.PropertyLabelEmbedded] = labelled + p.Memo = m + return p, nil +} + +// chooseSize settles the picture size. +// +// Named dimensions are used as given. Left out, the picture is grown to fill +// the request, because the pixels are stored uncompressed and the size is +// therefore arithmetic rather than whatever an encoder decides. What is left +// over goes into the gap. +func chooseSize(r format.Request) (int, int, error) { + _, wSet := r.Properties["width"] + _, hSet := r.Properties["height"] + + if wSet || hSet { + w, err := dimension(r.Properties, "width", 0) + if err != nil { + return 0, 0, err + } + h, err := dimension(r.Properties, "height", 0) + if err != nil { + return 0, 0, err + } + switch { + case wSet && hSet: + return w, h, nil + case wSet: + // One side named, the other worked out from what is left. + return w, fill(r.Bytes, w), nil + default: + return fillWidth(r.Bytes, h), h, nil + } + } + + avail := r.Bytes - overhead + if avail < samplesPerPixel { + return minDimension, minDimension, nil + } + // A square puts the most pixels into the request and is what a person + // expects to see when they did not say otherwise. + w := int(isqrt(uint64(avail / samplesPerPixel))) + if w < minDimension { + w = minDimension + } + if w > maxDimension { + w = maxDimension + } + for w > minDimension && pixelBytes(w, 1) > avail { + w-- + } + return w, fill(r.Bytes, w), nil +} + +// fill is the tallest picture of this width that still fits, at least one row. +func fill(bytes int64, width int) int { + avail := bytes - overhead + rowBytes := pixelBytes(width, 1) + if rowBytes <= 0 || avail < rowBytes { + return minDimension + } + h := avail / rowBytes + if h > maxDimension { + h = maxDimension + } + return int(h) +} + +// fillWidth is the widest picture of this height that still fits. +func fillWidth(bytes int64, height int) int { + avail := bytes - overhead + if height < 1 || avail < samplesPerPixel { + return minDimension + } + w := avail / (int64(height) * samplesPerPixel) + if w > maxDimension { + w = maxDimension + } + if w < minDimension { + w = minDimension + } + for w > minDimension && pixelBytes(int(w), height) > avail { + w-- + } + return int(w) +} + +// isqrt is an integer square root, so that the picture a size produces is the +// same on every machine. Floating point would almost certainly agree, and +// "almost certainly" is not what byte stability across platforms means. +func isqrt(n uint64) uint64 { + if n == 0 { + return 0 + } + x := n + y := (x + 1) / 2 + for y < x { + x = y + y = (x + n/x) / 2 + } + return x +} + +func dimension(props map[string]string, key string, fallback int) (int, error) { + raw, ok := props[key] + if !ok || raw == "" { + return fallback, nil + } + n, err := strconv.Atoi(raw) + if err != nil { + return 0, fmt.Errorf("tiff: %s must be a whole number of pixels, got %q", key, raw) + } + if n < minDimension || n > maxDimension { + return 0, fmt.Errorf("tiff: %s must be between %d and %d pixels, got %d", key, minDimension, maxDimension, n) + } + return n, nil +} + +func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error { + m, ok := p.Memo.(memo) + if !ok { + return fmt.Errorf("tiff: the plan was not produced by this generator") + } + + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + if err := writeHeader(w, m); err != nil { + return err + } + if err := writeGap(ctx, w, m.seed, m.gap); err != nil { + return err + } + if err := writePixels(ctx, w, m); err != nil { + return err + } + return writeDirectory(w, m) +} + +// pixelsAt is where the image data begins, which is what StripOffsets holds. +func pixelsAt(m memo) int64 { + return header + m.gap +} + +// directoryAt is where the IFD begins, which is what the header points at. +func directoryAt(m memo) int64 { + return pixelsAt(m) + pixelBytes(m.width, m.height) +} + +func writeHeader(w io.Writer, m memo) error { + var head [header]byte + head[0], head[1] = 'I', 'I' + binary.LittleEndian.PutUint16(head[2:4], 42) + binary.LittleEndian.PutUint32(head[4:8], uint32(directoryAt(m))) + _, err := w.Write(head[:]) + return err +} + +// writeDirectory emits the IFD and the values that did not fit inside it. +// +// The entries have to be in ascending tag order, which the specification +// requires and readers rely on. The heap follows the directory, so its offsets +// are known once the directory length is - and that length is a constant here, +// because the set of tags never changes. +func writeDirectory(w io.Writer, m memo) error { + heapAt := uint32(directoryAt(m) + directory) + + var buf []byte + buf = binary.LittleEndian.AppendUint16(buf, entryCount) + + // Values longer than four bytes live in the heap, in the order they are + // referenced here. + bitsAt := heapAt + xResAt := bitsAt + samplesPerPixel*2 + yResAt := xResAt + 8 + + entry := func(tag, kind uint16, count, value uint32) { + buf = binary.LittleEndian.AppendUint16(buf, tag) + buf = binary.LittleEndian.AppendUint16(buf, kind) + buf = binary.LittleEndian.AppendUint32(buf, count) + buf = binary.LittleEndian.AppendUint32(buf, value) + } + // A SHORT that fits in the four byte value field sits in its low half. + short := func(tag uint16, v uint16) { + entry(tag, typeShort, 1, uint32(v)) + } + + short(tagImageWidth, uint16(m.width)) + short(tagImageLength, uint16(m.height)) + entry(tagBitsPerSample, typeShort, samplesPerPixel, bitsAt) + short(tagCompression, 1) // none + short(tagPhotometric, 2) // RGB + entry(tagStripOffsets, typeLong, 1, uint32(pixelsAt(m))) + short(tagSamplesPerPixel, samplesPerPixel) + short(tagRowsPerStrip, uint16(m.height)) + entry(tagStripByteCounts, typeLong, 1, uint32(pixelBytes(m.width, m.height))) + entry(tagXResolution, typeRational, 1, xResAt) + entry(tagYResolution, typeRational, 1, yResAt) + short(tagResolutionUnit, 2) // inches + + // No second directory. + buf = binary.LittleEndian.AppendUint32(buf, 0) + + // The heap, in the order the entries above point at it. + for i := 0; i < samplesPerPixel; i++ { + buf = binary.LittleEndian.AppendUint16(buf, bitsPerSample) + } + // 72 dpi, written as the rational 72/1, which is what most tools write. + for i := 0; i < 2; i++ { + buf = binary.LittleEndian.AppendUint32(buf, 72) + buf = binary.LittleEndian.AppendUint32(buf, 1) + } + + if len(buf) != directory+heap { + return fmt.Errorf("tiff: the directory came out %d B and the header says %d B", len(buf), directory+heap) + } + _, err := w.Write(buf) + return err +} + +// gapChunkSize is how much filler is built before each write. It also sets how +// often cancellation is noticed. +const gapChunkSize = 32 * 1024 + +// writeGap emits the padding without ever holding it in memory. The gap can be +// most of the file when the dimensions were named, so a fixed buffer is the +// difference between a large file and a failed run. +func writeGap(ctx context.Context, w io.Writer, seed uint64, n int64) error { + if n <= 0 { + return nil + } + rng := core.NewRand(seed) + size := int64(gapChunkSize) + if n < size { + size = n + } + buf := make([]byte, size) + for remaining := n; remaining > 0; { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + take := int64(len(buf)) + if remaining < take { + take = remaining + } + chunk := buf[:take] + for i := 0; i < len(chunk); i += 8 { + var eight [8]byte + binary.BigEndian.PutUint64(eight[:], rng.Uint64()) + copy(chunk[i:], eight[:]) + } + if _, err := w.Write(chunk); err != nil { + return err + } + remaining -= take + } + return nil +} + +// writePixels streams the picture a row at a time. +// +// Only the label band is ever built as an image, and it is at most 24 rows +// tall whatever the picture is. Everything else comes from the formula +// straight into one reused row buffer, so a 64 MiB picture costs a row rather +// than 64 MiB. +// +// A TIFF stores its rows top down, so the label band is written first - the +// one place this is simpler than BMP, which stores them the other way up. +func writePixels(ctx context.Context, w io.Writer, m memo) error { + row := make([]byte, m.width*samplesPerPixel) + off := int(m.seed % 256) + + band := labelBand(m, off) + bandH := 0 + if band != nil { + bandH = band.Bounds().Dy() + } + + for y := 0; y < m.height; y++ { + if y%64 == 0 { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + } + if y < bandH { + copyBandRow(row, band, y, m.width) + } else { + fillRow(row, y, m.width, off) + } + if _, err := w.Write(row); err != nil { + return err + } + } + return nil +} + +// fillRow writes one row of the gradient in the order a TIFF wants it: red, +// green, blue. +func fillRow(row []byte, y, width, off int) { + for x := 0; x < width; x++ { + row[x*3] = uint8((x + off) % 256) + row[x*3+1] = uint8((y + off) % 256) + row[x*3+2] = uint8((x + y + off) % 256) + } +} + +func copyBandRow(row []byte, band *image.RGBA, y, width int) { + for x := 0; x < width; x++ { + i := band.PixOffset(x, y) + row[x*3] = band.Pix[i] + row[x*3+1] = band.Pix[i+1] + row[x*3+2] = band.Pix[i+2] + } +} + +// labelBand rasterises the top strip of the picture, label included, or +// returns nil when there is no label to draw. +func labelBand(m memo, off int) *image.RGBA { + if m.label == "" || !imagelabel.Fits(m.width, len(m.label)) { + return nil + } + h := maxBandHeight + if m.height < h { + h = m.height + } + img := image.NewRGBA(image.Rect(0, 0, m.width, h)) + for y := 0; y < h; y++ { + for x := 0; x < m.width; x++ { + img.SetRGBA(x, y, color.RGBA{ + R: uint8((x + off) % 256), + G: uint8((y + off) % 256), + B: uint8((x + y + off) % 256), + A: 255, + }) + } + } + imagelabel.Draw(img, m.label) + return img +} diff --git a/internal/guard/branching_test.go b/internal/guard/branching_test.go index 38d7e4c..bd95f6f 100644 --- a/internal/guard/branching_test.go +++ b/internal/guard/branching_test.go @@ -27,16 +27,23 @@ const ( // Depth is absolute because a percentage needs a range to be a percentage // OF, and depth here runs 0 to 4. Three quarters of 4 is 3, which happens // to be right today - but the moment the ceiling dropped to 3 the band - // would become "2 or more" and the count would jump from 53 into the + // would become "2 or more" and the count would jump from 54 into the // hundreds. A band that reshapes itself under the thing it watches says // nothing. crowdingComplexity = 17 crowdingArguments = 7 crowdingDepth = 3 - crowdedComplexity = 5 - crowdedArguments = 5 - crowdedDepthFunctions = 53 + crowdedComplexity = 5 + crowdedArguments = 5 + // 53 until 2026-08-29, when TIFF arrived. The function that took it to 54 + // is tiff.chooseSize, and it is the same shape as bmp.chooseSize because + // the two formats do the same arithmetic - the picture is grown to fill + // the request, so one branch handles both dimensions named, one handles + // either, and one handles neither. Flattening it in TIFF alone would make + // two functions that answer the same question look different, which costs + // more than the depth does. + crowdedDepthFunctions = 54 // An axis this set does not watch. crowding() asks n >= band, so nothing // reaches it. diff --git a/internal/guard/generatorbytes_test.go b/internal/guard/generatorbytes_test.go index 3398fe2..627d8b6 100644 --- a/internal/guard/generatorbytes_test.go +++ b/internal/guard/generatorbytes_test.go @@ -66,10 +66,25 @@ func goldenCases() map[string]engine.Target { "bmp_64kib": {ID: "g", Format: "bmp", Sizes: engine.Uniform(1, 65536), Label: true, Properties: map[string]string{"width": "64", "height": "64"}}, - // The one format whose picture is grown to fill the request, so the + // The two formats whose picture is grown to fill the request, so the // dimensions are arithmetic rather than a setting. Pinned without them, // because that arithmetic is what a refactor would move. + // + // It was one until 2026-08-29. TIFF stores its pixels uncompressed + // too, so it is built the same way and needs the same pair of cases. "bmp_100kib_sized_to_fit": {ID: "g", Format: "bmp", Sizes: engine.Uniform(1, 102400), Label: true}, + "tiff_64kib": {ID: "g", Format: "tiff", Sizes: engine.Uniform(1, 65536), Label: true, + Properties: map[string]string{"width": "64", "height": "64"}}, + "tiff_100kib_sized_to_fit": {ID: "g", Format: "tiff", Sizes: engine.Uniform(1, 102400), Label: true}, + + // Naming one side and letting the other be worked out is its own + // branch, and it had no case until 2026-08-29. The mutation that + // removes that arithmetic came back NOT CAUGHT with the two cases + // above in place: one names both sides and the other names neither, + // so neither of them ever reached it. That was a hole in the evidence + // rather than in the code, and this is what closes it. + "tiff_64kib_width_only": {ID: "g", Format: "tiff", Sizes: engine.Uniform(1, 65536), Label: true, + Properties: map[string]string{"width": "128"}}, "gif_64kib": {ID: "g", Format: "gif", Sizes: engine.Uniform(1, 65536), Label: true, Properties: map[string]string{"width": "64", "height": "64"}}, "ico_32kib": {ID: "g", Format: "ico", Sizes: engine.Uniform(1, 32768), Label: true, diff --git a/internal/guard/layers_test.go b/internal/guard/layers_test.go index 0484383..1398aeb 100644 --- a/internal/guard/layers_test.go +++ b/internal/guard/layers_test.go @@ -56,6 +56,7 @@ var layer = map[string]int{ "internal/format/pdf": 1, "internal/format/zip": 1, "internal/format/targz": 1, + "internal/format/tiff": 1, "internal/format/wav": 1, "internal/recipe": 2, @@ -102,6 +103,7 @@ var sameLayerAllowed = map[string][]string{ "internal/format/pdf", "internal/format/zip", "internal/format/targz", + "internal/format/tiff", "internal/format/wav", }, "internal/format/imagelabel": {"internal/format"}, @@ -125,6 +127,7 @@ var sameLayerAllowed = map[string][]string{ "internal/format/pdf": {"internal/format", "internal/format/imagelabel"}, "internal/format/zip": {"internal/format", "internal/format/imagelabel"}, "internal/format/targz": {"internal/format"}, + "internal/format/tiff": {"internal/format", "internal/format/imagelabel"}, "internal/format/wav": {"internal/format", "internal/format/imagelabel"}, "internal/preset": {"internal/recipe"}, diff --git a/internal/guard/oracle_test.go b/internal/guard/oracle_test.go index f68af33..f090c21 100644 --- a/internal/guard/oracle_test.go +++ b/internal/guard/oracle_test.go @@ -135,13 +135,15 @@ func TestEveryFormatSurvivesItsReferenceTool(t *testing.T) { // structurally - in silence. The drift between the two lists was guarded, the // absence from both was not. // -// Five Tier 1 formats are still to come and all five are binary, which is where +// Four Tier 1 formats are still to come and all four are binary, which is where // the structural check earns most: at JPG it caught bytes after EOI that Pillow -// read without complaint. +// read without complaint, and at TIFF it is the only layer that sees a +// directory lying about how many bytes of pixels there are - measured +// 2026-08-29 on five readers, four of which accepted that file. var structurallyChecked = map[string]bool{ "png": true, "wav": true, "pdf": true, "zip": true, "targz": true, "log": true, "csv": true, "json": true, "xml": true, "svg": true, "html": true, - "bmp": true, "gif": true, "ico": true, "jpg": true, + "bmp": true, "gif": true, "ico": true, "jpg": true, "tiff": true, "docx": true, "xlsx": true, "pptx": true, // Nothing to check against beyond "these are the bytes we meant", so they // have one layer and it is honest to say so out loud. diff --git a/internal/guard/parity_test.go b/internal/guard/parity_test.go index a96346c..895f334 100644 --- a/internal/guard/parity_test.go +++ b/internal/guard/parity_test.go @@ -83,6 +83,7 @@ var reachableFromTheWindow = []string{ "format:xlsx", "format:svg", "format:targz", + "format:tiff", "format:txt", "format:wav", "format:xml", @@ -115,6 +116,8 @@ var reachableFromTheWindow = []string{ "property:targz.entries", "property:targz.entry_format", "property:targz.entry_size", + "property:tiff.height", + "property:tiff.width", "property:wav.bit_depth", "property:wav.channels", "property:wav.content", diff --git a/internal/guard/testdata/generator-golden.json b/internal/guard/testdata/generator-golden.json index 2b8b22c..ffaa4ee 100644 --- a/internal/guard/testdata/generator-golden.json +++ b/internal/guard/testdata/generator-golden.json @@ -110,6 +110,21 @@ "sha256": "84a776cc5439d9a7edb29018dca7b0fb1462f7b57a374de34c21b0cf7f601eb5", "measured_on": "2026-08-04" }, + "tiff_100kib_sized_to_fit": { + "bytes": 102400, + "sha256": "172e2e47dda124de9cdbc122b55d25c2fc20de4c78ab963bbf6c7645da247bbd", + "measured_on": "2026-08-29" + }, + "tiff_64kib_width_only": { + "bytes": 65536, + "sha256": "c5c38af41809c7e98333fc2a6bdcca36a955f70ab4413dbfde9b9ec4dd3d0b8f", + "measured_on": "2026-08-29" + }, + "tiff_64kib": { + "bytes": 65536, + "sha256": "4d174b53a1603f63b1cd49307f793540190b2d03e26f60b91ca767a93d610340", + "measured_on": "2026-08-29" + }, "txt_4kib": { "bytes": 4096, "sha256": "26a697bd184868acaa1da3f474b32a063e1b76e55ced42e38e79fdc63cf66161" diff --git a/internal/guard/testdata/screens/generate-menu-hovered.png b/internal/guard/testdata/screens/generate-menu-hovered.png index b6e2318..7918543 100644 Binary files a/internal/guard/testdata/screens/generate-menu-hovered.png and b/internal/guard/testdata/screens/generate-menu-hovered.png differ diff --git a/internal/guard/testdata/screens/generate-menu-hovered.xml b/internal/guard/testdata/screens/generate-menu-hovered.xml index f6bfddf..d49aa7a 100644 --- a/internal/guard/testdata/screens/generate-menu-hovered.xml +++ b/internal/guard/testdata/screens/generate-menu-hovered.xml @@ -428,7 +428,7 @@ - + @@ -525,8 +525,8 @@ - - + + diff --git a/internal/guard/testdata/screens/generate-menu-keyed.png b/internal/guard/testdata/screens/generate-menu-keyed.png index fb60f50..7ef3921 100644 Binary files a/internal/guard/testdata/screens/generate-menu-keyed.png and b/internal/guard/testdata/screens/generate-menu-keyed.png differ diff --git a/internal/guard/testdata/screens/generate-menu-keyed.xml b/internal/guard/testdata/screens/generate-menu-keyed.xml index 437af8c..a33bd5d 100644 --- a/internal/guard/testdata/screens/generate-menu-keyed.xml +++ b/internal/guard/testdata/screens/generate-menu-keyed.xml @@ -428,7 +428,7 @@ - + @@ -525,8 +525,8 @@ - - + + diff --git a/internal/guard/testdata/screens/generate-menu.png b/internal/guard/testdata/screens/generate-menu.png index 03a9ffa..27ed2a3 100644 Binary files a/internal/guard/testdata/screens/generate-menu.png and b/internal/guard/testdata/screens/generate-menu.png differ diff --git a/internal/guard/testdata/screens/generate-menu.xml b/internal/guard/testdata/screens/generate-menu.xml index 53a2434..108dfff 100644 --- a/internal/guard/testdata/screens/generate-menu.xml +++ b/internal/guard/testdata/screens/generate-menu.xml @@ -428,7 +428,7 @@ - + @@ -525,8 +525,8 @@ - - + + diff --git a/internal/guard/testdata/screens/preset-menu-setting.png b/internal/guard/testdata/screens/preset-menu-setting.png index 7f81c04..8e0fe3f 100644 Binary files a/internal/guard/testdata/screens/preset-menu-setting.png and b/internal/guard/testdata/screens/preset-menu-setting.png differ diff --git a/internal/guard/testdata/screens/preset-menu-setting.xml b/internal/guard/testdata/screens/preset-menu-setting.xml index 1a9b274..33d6c61 100644 --- a/internal/guard/testdata/screens/preset-menu-setting.xml +++ b/internal/guard/testdata/screens/preset-menu-setting.xml @@ -416,7 +416,7 @@ - + @@ -516,8 +516,8 @@ - - + + diff --git a/internal/guard/textformats_test.go b/internal/guard/textformats_test.go index 832f200..c667a85 100644 --- a/internal/guard/textformats_test.go +++ b/internal/guard/textformats_test.go @@ -446,7 +446,7 @@ func TestAnSVGDrawingCarriesRealShapes(t *testing.T) { // already written down cannot, by construction, find what is missing from them. var textFormats = []string{"txt", "md", "log", "csv", "json", "xml", "html", "svg"} -var binaryFormats = []string{"bmp", "docx", "gif", "ico", "jpg", "pdf", "png", "pptx", "targz", "wav", "xlsx", "zip"} +var binaryFormats = []string{"bmp", "docx", "gif", "ico", "jpg", "pdf", "png", "pptx", "targz", "tiff", "wav", "xlsx", "zip"} // Every registered format is on exactly one of the two lists above. // diff --git a/internal/gui/parts/filekind.go b/internal/gui/parts/filekind.go index 3fc13b5..2c7dd4a 100644 --- a/internal/gui/parts/filekind.go +++ b/internal/gui/parts/filekind.go @@ -92,6 +92,7 @@ var fileKinds = map[string]fileKind{ "jpg": kindPicture, "png": kindPicture, "svg": kindPicture, + "tiff": kindPicture, "docx": kindDocument, "pdf": kindDocument, "pptx": kindDocument, diff --git a/internal/oracle/oracle.go b/internal/oracle/oracle.go index 89449a5..aee677b 100644 --- a/internal/oracle/oracle.go +++ b/internal/oracle/oracle.go @@ -351,7 +351,7 @@ func Strict(formatID, path string) Result { func StrictKnows(formatID string) bool { switch formatID { case "png", "wav", "pdf", "zip", "targz", "log", "csv", "json", "xml", "svg", "html", - "bmp", "gif", "ico", "jpg", "docx", "xlsx", "pptx": + "bmp", "gif", "ico", "jpg", "tiff", "docx", "xlsx", "pptx": return true } return false diff --git a/internal/oracle/strict.py b/internal/oracle/strict.py index 397350c..4b49db0 100644 --- a/internal/oracle/strict.py +++ b/internal/oracle/strict.py @@ -1094,6 +1094,115 @@ def check_jpg(data): f"{comments} comment(s) carrying {comment_bytes} B") +def check_tiff(data): + # This layer earns more at TIFF than anywhere else, and that is measured + # rather than assumed. On 2026-08-29 six deliberately broken files went + # through five readers: a StripByteCounts announcing half the pixel data + # was accepted by FOUR of them - Pillow, exiftool, WIC and GDI+ - and + # caught only by x/image. A reader that decodes the image it can find does + # not have to care that the directory lied about how much there was. + if data[:2] != b"II": + fail("the byte order mark is not II, and this generator writes little-endian only") + if len(data) < 8: + fail(f"the file is {len(data)} B and the header alone is 8 B") + + magic = struct.unpack(" len(data): + fail(f"the directory is said to start at {ifd_at} and the file ends at {len(data)}") + + count = struct.unpack(" len(data): + fail(f"a directory of {count} entries runs to {end} and the file ends at {len(data)}") + + sizes = {1: 1, 2: 1, 3: 2, 4: 4, 5: 8, 7: 1} + tags, previous = {}, -1 + for i in range(count): + at = ifd_at + 2 + 12 * i + tag, kind, n = struct.unpack(" 4: + off = struct.unpack(" len(data): + fail(f"the value of tag {tag} runs to {off + total} and the file ends at {len(data)}") + value = data[off:off + total] + else: + value = raw[:total] + tags[tag] = (kind, n, value) + + nxt = struct.unpack(" len(data): + fail(f"the pixels run to {offset + counts} and the file ends at {len(data)}") + # The pixels have to end where the directory begins, because this + # generator puts its padding in front of them and nothing between. + if offset + counts != ifd_at: + fail(f"the pixels end at {offset + counts} and the directory begins at {ifd_at}") + + gap = offset - 8 + ok(f"{width}x{height}, {samples * 8} bit, uncompressed, gap {gap} B, " + f"{count} entries in one directory, byte counts agree with the geometry") + + def check_docx(data): opc_check(data, ["word/document.xml"], "Word document", "docx") @@ -1113,6 +1222,7 @@ def check_pptx(data): "log": check_log, "csv": check_csv, "json": check_json, "xml": check_xml, "svg": check_svg, "html": check_html, "targz": check_targz, "bmp": check_bmp, "gif": check_gif, "ico": check_ico, "jpg": check_jpg, + "tiff": check_tiff, "docx": check_docx, "xlsx": check_xlsx, "pptx": check_pptx} if __name__ == "__main__": diff --git a/web/assets/social-preview.png b/web/assets/social-preview.png index 83f0065..06a930c 100644 Binary files a/web/assets/social-preview.png and b/web/assets/social-preview.png differ diff --git a/web/content/en/index.html b/web/content/en/index.html index e453818..7ea172d 100644 --- a/web/content/en/index.html +++ b/web/content/en/index.html @@ -12,7 +12,7 @@

Generate real test files at any exact size

- The desktop window of Testing Files Generator, set up to write a batch of test files
The desktop window, set up to write a batch of files. The same engine runs behind the command line.
@@ -170,11 +170,12 @@

Pick the build for your system

desktop window. There is no installer and nothing to add to your machine.

{{ template "downloadsTable" . }} -
-

The binaries are not signed

+
+

What is signed, and what is not

- Your system will warn you the first time you run one. The release notes say exactly what each - system shows and why. Every archive is listed in verify-SHA256SUMS.txt on the release page, + The Windows and macOS downloads are signed, so they start without a warning about an unknown + developer. The Linux ones are not, because desktop Linux has no equivalent to sign them with. + Every archive is listed in verify-SHA256SUMS.txt on the release page, so you can check what you downloaded.

@@ -197,7 +198,7 @@

Where this is today

Not there yet: five more formats are planned - 7z, tiff, webp, mp3 and mp4. The preset catalogue has one entry so far. Some recipe keys are recognised and refused with a message saying they are not built yet, rather - than being ignored quietly. The binaries are not signed. + than being ignored quietly. The Linux downloads are unsigned.

Found a problem or want a format? The issue tracker is open, and diff --git a/web/content/en/site.json b/web/content/en/site.json index c3df018..f2ffea8 100644 --- a/web/content/en/site.json +++ b/web/content/en/site.json @@ -55,7 +55,7 @@ "ctaDownload": "Download", "ctaSource": "View the source", - "ctaNote": "Free and open source, GPL-3.0. Nothing to sign up for. The binaries are not signed yet, so your system will warn you the first time - the release notes say what to expect.", + "ctaNote": "Free and open source, GPL-3.0. Nothing to sign up for. The Windows and macOS downloads are signed and start without a warning. The Linux ones are unsigned.", "colFormat": "Format", "colExtension": "Extension", diff --git a/web/content/pl/index.html b/web/content/pl/index.html index 4541787..edea3b1 100644 --- a/web/content/pl/index.html +++ b/web/content/pl/index.html @@ -12,7 +12,7 @@

Generuj pliki testowe o zadanym rozmiarze

- Okno programu Testing Files Generator przygotowane do zapisania wsadu plików testowych
Okno programu przygotowane do zapisania wsadu plików. Za wierszem poleceń stoi ten sam silnik.
@@ -170,11 +170,12 @@

Wybierz wersję dla swojego systemu

Nie ma instalatora i nie trzeba niczego dokładać do systemu.

{{ template "downloadsTable" . }} -
-

Binarki nie są podpisane

+
+

Co jest podpisane, a co nie

- System ostrzeże przy pierwszym uruchomieniu. Nota wydania mówi dokładnie, co pokaże każdy system - i dlaczego. Każde archiwum jest wypisane w pliku verify-SHA256SUMS.txt na stronie wydania, + Pliki dla Windows i macOS są podpisane, więc uruchamiają się bez ostrzeżenia o nieznanym + wydawcy. Pliki dla Linuksa nie są, bo na Linuksie nie ma czym ich podpisać. + Każde archiwum jest wypisane w pliku verify-SHA256SUMS.txt na stronie wydania, więc możesz sprawdzić, co pobrałeś.

@@ -198,7 +199,7 @@

Gdzie to dziś jest

Czego jeszcze nie ma: pięciu zaplanowanych formatów - 7z, tiff, webp, mp3 i mp4. Katalog presetów ma na razie jedną pozycję. Część kluczy przepisu jest rozpoznawana i odrzucana komunikatem, że nie ma ich - w tej wersji, zamiast być po cichu ignorowana. Binarki nie są podpisane. + w tej wersji, zamiast być po cichu ignorowana. Pliki dla Linuksa nie są podpisane.

Znalazłeś błąd albo potrzebujesz formatu? Zgłoszenia są otwarte, diff --git a/web/content/pl/site.json b/web/content/pl/site.json index 3dbf8a9..f02c8c8 100644 --- a/web/content/pl/site.json +++ b/web/content/pl/site.json @@ -55,7 +55,7 @@ "ctaDownload": "Pobierz", "ctaSource": "Zobacz kod źródłowy", - "ctaNote": "Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Binarki nie są jeszcze podpisane, więc system ostrzeże przy pierwszym uruchomieniu - nota wydania mówi dokładnie, czego się spodziewać.", + "ctaNote": "Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Pliki dla Windows i macOS są podpisane i uruchamiają się bez ostrzeżenia. Pliki dla Linuksa nie są podpisane.", "colFormat": "Format", "colExtension": "Rozszerzenie", diff --git a/web/public/assets/social-preview.png b/web/public/assets/social-preview.png index 83f0065..06a930c 100644 Binary files a/web/public/assets/social-preview.png and b/web/public/assets/social-preview.png differ diff --git a/web/public/assets/window.png b/web/public/assets/window.png index 6f39731..6a7fa02 100644 Binary files a/web/public/assets/window.png and b/web/public/assets/window.png differ diff --git a/web/public/create-file-exact-size/index.html b/web/public/create-file-exact-size/index.html index a99f44f..e44ded8 100644 --- a/web/public/create-file-exact-size/index.html +++ b/web/public/create-file-exact-size/index.html @@ -196,7 +196,7 @@

A real file of that format, at exactly the size you asked for

Download 0.2.0 View the source
-

Free and open source, GPL-3.0. Nothing to sign up for. The binaries are not signed yet, so your system will warn you the first time - the release notes say what to expect.

+

Free and open source, GPL-3.0. Nothing to sign up for. The Windows and macOS downloads are signed and start without a warning. The Linux ones are unsigned.

diff --git a/web/public/faq/index.html b/web/public/faq/index.html index 5a4c85f..32211bd 100644 --- a/web/public/faq/index.html +++ b/web/public/faq/index.html @@ -134,7 +134,7 @@

Frequently asked questions

Which formats are coming next?

-

7z, tiff, webp, mp3 and mp4. 20 formats work end to end today.

+

7z, tiff, webp, mp3 and mp4. 21 formats work end to end today.

@@ -205,7 +205,7 @@

Frequently asked questions

{ "@type": "Question", "name": "Which formats are coming next?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, tiff, webp, mp3 and mp4. 20 formats work end to end today." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, tiff, webp, mp3 and mp4. 21 formats work end to end today." } }, { "@type": "Question", @@ -237,7 +237,7 @@

Still deciding?

Download 0.2.0 View the source -

Free and open source, GPL-3.0. Nothing to sign up for. The binaries are not signed yet, so your system will warn you the first time - the release notes say what to expect.

+

Free and open source, GPL-3.0. Nothing to sign up for. The Windows and macOS downloads are signed and start without a warning. The Linux ones are unsigned.

diff --git a/web/public/formats/index.html b/web/public/formats/index.html index 5b5d43b..41449da 100644 --- a/web/public/formats/index.html +++ b/web/public/formats/index.html @@ -3,7 +3,7 @@ -20 Supported File Formats - PDF, DOCX, PNG, ZIP and More +21 Supported File Formats - PDF, DOCX, PNG, ZIP and More @@ -12,7 +12,7 @@ - + @@ -21,7 +21,7 @@ - + @@ -74,7 +74,7 @@
-

20 file formats, every one generated at an exact size

+

21 file formats, every one generated at an exact size

Each of these is a real file of that format. It opens in the software that owns it, and it is exactly the number of bytes you asked for. None of them is padded zeros with an extension @@ -198,6 +198,13 @@

20 file formats, every one generated at an exact size

full 7z + + tiff + .tiff + 183 + full + pillow + txt .txt @@ -382,6 +389,16 @@

Settings each format accepts

entry_size a size such as 2mb + + tiff + width + 1 - 20000 pixels + + + + height + 1 - 20000 pixels + wav sample_rate diff --git a/web/public/index.html b/web/public/index.html index a45aabd..f7e2926 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -3,7 +3,7 @@ -Test File Generator for QA - Exact Size, 20 Real Formats +Test File Generator for QA - Exact Size, 21 Real Formats @@ -12,7 +12,7 @@ - + @@ -21,7 +21,7 @@ - + @@ -87,7 +87,7 @@

Generate real test files at any exact size

- PDF, PNG, DOCX, ZIP - 20 formats in all, and every one is a + PDF, PNG, DOCX, ZIP - 21 formats in all, and every one is a real file that opens in the software that owns it, at exactly the size you asked for. Each run also writes down what your application is supposed to do with each file. Command line and desktop window, free and open source, working entirely on your machine. @@ -97,11 +97,11 @@

Generate real test files at any exact size

Download 0.2.0 View the source
-

Free and open source, GPL-3.0. Nothing to sign up for. The binaries are not signed yet, so your system will warn you the first time - the release notes say what to expect.

+

Free and open source, GPL-3.0. Nothing to sign up for. The Windows and macOS downloads are signed and start without a warning. The Linux ones are unsigned.

- The desktop window of Testing Files Generator, set up to write a batch of test files
The desktop window, set up to write a batch of files. The same engine runs behind the command line.
@@ -109,7 +109,7 @@

Generate real test files at any exact size

  • - 20 + 21

    real formats, each one opening in the software that owns it

  • @@ -229,7 +229,7 @@

    Built for a suite that runs unattended

    Ask for 10485761 bytes and get exactly that. A size a format cannot reach is an error with a reason, never a file of the wrong size.

  • -

    20 real formats

    +

    21 real formats

    Not padded zeros with an extension. A generated PNG opens in an image viewer, a DOCX opens in Word, a ZIP extracts. Each one is checked against independent readers before it ships.

  • @@ -286,11 +286,12 @@

    Pick the build for your system

    -
    -

    The binaries are not signed

    +
    +

    What is signed, and what is not

    - Your system will warn you the first time you run one. The release notes say exactly what each - system shows and why. Every archive is listed in verify-SHA256SUMS.txt on the release page, + The Windows and macOS downloads are signed, so they start without a warning about an unknown + developer. The Linux ones are not, because desktop Linux has no equivalent to sign them with. + Every archive is listed in verify-SHA256SUMS.txt on the release page, so you can check what you downloaded.

    @@ -298,7 +299,7 @@

    Pick the build for your system

    Download 0.2.0 View the source
    -

    Free and open source, GPL-3.0. Nothing to sign up for. The binaries are not signed yet, so your system will warn you the first time - the release notes say what to expect.

    +

    Free and open source, GPL-3.0. Nothing to sign up for. The Windows and macOS downloads are signed and start without a warning. The Linux ones are unsigned.

    @@ -309,7 +310,7 @@

    Where this is today

    state of it.

    - Working end to end: 20 formats, recipes, presets, the desktop + Working end to end: 21 formats, recipes, presets, the desktop window, generate, validate, verify, cleanup, boundary sets, archive contents, size ranges, per format settings, manifests and every exit code.

    @@ -317,7 +318,7 @@

    Where this is today

    Not there yet: five more formats are planned - 7z, tiff, webp, mp3 and mp4. The preset catalogue has one entry so far. Some recipe keys are recognised and refused with a message saying they are not built yet, rather - than being ignored quietly. The binaries are not signed. + than being ignored quietly. The Linux downloads are unsigned.

    Found a problem or want a format? The issue tracker is open, and diff --git a/web/public/pl/faq/index.html b/web/public/pl/faq/index.html index 7be3e38..e41f24e 100644 --- a/web/public/pl/faq/index.html +++ b/web/public/pl/faq/index.html @@ -135,7 +135,7 @@

    Najczęstsze pytania

    Jakie formaty są następne w kolejce?

    -

    7z, tiff, webp, mp3 i mp4. Formatów działających dziś od początku do końca jest 20.

    +

    7z, tiff, webp, mp3 i mp4. Formatów działających dziś od początku do końca jest 21.

    @@ -206,7 +206,7 @@

    Najczęstsze pytania

    { "@type": "Question", "name": "Jakie formaty są następne w kolejce?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, tiff, webp, mp3 i mp4. Formatów działających dziś od początku do końca jest 20." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, tiff, webp, mp3 i mp4. Formatów działających dziś od początku do końca jest 21." } }, { "@type": "Question", @@ -238,7 +238,7 @@

    Wciąż się zastanawiasz?

    Pobierz 0.2.0 Zobacz kod źródłowy -

    Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Binarki nie są jeszcze podpisane, więc system ostrzeże przy pierwszym uruchomieniu - nota wydania mówi dokładnie, czego się spodziewać.

    +

    Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Pliki dla Windows i macOS są podpisane i uruchamiają się bez ostrzeżenia. Pliki dla Linuksa nie są podpisane.

diff --git a/web/public/pl/formaty/index.html b/web/public/pl/formaty/index.html index 55ddd04..370d63d 100644 --- a/web/public/pl/formaty/index.html +++ b/web/public/pl/formaty/index.html @@ -3,7 +3,7 @@ -20 formatów plików testowych - PDF, DOCX, PNG, ZIP +21 formatów plików testowych - PDF, DOCX, PNG, ZIP @@ -12,7 +12,7 @@ - + @@ -21,7 +21,7 @@ - + @@ -74,7 +74,7 @@
-

20 formatów plików, każdy generowany o dokładnym rozmiarze

+

21 formatów plików, każdy generowany o dokładnym rozmiarze

Każdy z nich to prawdziwy plik tego formatu. Otwiera się w programie, do którego należy, i ma dokładnie tyle bajtów, ile zamówiłeś. Żaden nie jest zerami z doklejonym rozszerzeniem. @@ -197,6 +197,13 @@

20 formatów plików, każdy generowany o dokładnym rozmiarze

full 7z + + tiff + .tiff + 183 + full + pillow + txt .txt @@ -382,6 +389,16 @@

Ustawienia, które przyjmuje każdy format

entry_size rozmiar, na przykład 2mb + + tiff + width + 1 - 20000 pikseli + + + + height + 1 - 20000 pikseli + wav sample_rate diff --git a/web/public/pl/index.html b/web/public/pl/index.html index 2ba4ecd..8e904b9 100644 --- a/web/public/pl/index.html +++ b/web/public/pl/index.html @@ -3,7 +3,7 @@ -Generator plików testowych o zadanym rozmiarze - 20 formatów +Generator plików testowych o zadanym rozmiarze - 21 formatów @@ -12,7 +12,7 @@ - + @@ -21,7 +21,7 @@ - + @@ -87,7 +87,7 @@

Generuj pliki testowe o zadanym rozmiarze

- PDF, PNG, DOCX, ZIP - razem 20 formatów, a każdy to + PDF, PNG, DOCX, ZIP - razem 21 formatów, a każdy to prawdziwy plik, który otwiera się w programie, do którego należy, i ma dokładnie taki rozmiar, o jaki poprosisz. Każdy przebieg zapisuje też, co Twoja aplikacja ma z każdym plikiem zrobić. Wiersz poleceń i okno, darmowe i otwarte, działające wyłącznie na Twojej maszynie. @@ -97,11 +97,11 @@

Generuj pliki testowe o zadanym rozmiarze

Pobierz 0.2.0 Zobacz kod źródłowy
-

Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Binarki nie są jeszcze podpisane, więc system ostrzeże przy pierwszym uruchomieniu - nota wydania mówi dokładnie, czego się spodziewać.

+

Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Pliki dla Windows i macOS są podpisane i uruchamiają się bez ostrzeżenia. Pliki dla Linuksa nie są podpisane.

- Okno programu Testing Files Generator przygotowane do zapisania wsadu plików testowych
Okno programu przygotowane do zapisania wsadu plików. Za wierszem poleceń stoi ten sam silnik.
@@ -109,7 +109,7 @@

Generuj pliki testowe o zadanym rozmiarze

  • - 20 + 21

    prawdziwych formatów, każdy otwiera się w programie, do którego należy

  • @@ -229,7 +229,7 @@

    Zbudowane pod zestaw, który chodzi bez nadzoru

    Poproś o 10485761 bajtów i tyle dostaniesz. Rozmiar nieosiągalny dla formatu to błąd z powodem, nigdy plik o innym rozmiarze.

  • -

    20 prawdziwych formatów

    +

    21 prawdziwych formatów

    Nie zera z doklejonym rozszerzeniem. Wygenerowany PNG otwiera się w przeglądarce obrazów, DOCX w Wordzie, a ZIP się rozpakowuje. Każdy format jest sprawdzany niezależnym czytnikiem, zanim trafi do wydania.

  • @@ -286,11 +286,12 @@

    Wybierz wersję dla swojego systemu

    -
    -

    Binarki nie są podpisane

    +
    +

    Co jest podpisane, a co nie

    - System ostrzeże przy pierwszym uruchomieniu. Nota wydania mówi dokładnie, co pokaże każdy system - i dlaczego. Każde archiwum jest wypisane w pliku verify-SHA256SUMS.txt na stronie wydania, + Pliki dla Windows i macOS są podpisane, więc uruchamiają się bez ostrzeżenia o nieznanym + wydawcy. Pliki dla Linuksa nie są, bo na Linuksie nie ma czym ich podpisać. + Każde archiwum jest wypisane w pliku verify-SHA256SUMS.txt na stronie wydania, więc możesz sprawdzić, co pobrałeś.

    @@ -298,7 +299,7 @@

    Wybierz wersję dla swojego systemu

    Pobierz 0.2.0 Zobacz kod źródłowy
    -

    Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Binarki nie są jeszcze podpisane, więc system ostrzeże przy pierwszym uruchomieniu - nota wydania mówi dokładnie, czego się spodziewać.

    +

    Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Pliki dla Windows i macOS są podpisane i uruchamiają się bez ostrzeżenia. Pliki dla Linuksa nie są podpisane.

    @@ -309,7 +310,7 @@

    Gdzie to dziś jest

    faktyczny.

    - Działa od początku do końca: 20 formatów, przepisy, presety, + Działa od początku do końca: 21 formatów, przepisy, presety, okno, generate, validate, verify, cleanup, zestawy graniczne, zawartość archiwów, zakresy rozmiarów, ustawienia formatów, manifesty i wszystkie kody wyjścia. @@ -318,7 +319,7 @@

    Gdzie to dziś jest

    Czego jeszcze nie ma: pięciu zaplanowanych formatów - 7z, tiff, webp, mp3 i mp4. Katalog presetów ma na razie jedną pozycję. Część kluczy przepisu jest rozpoznawana i odrzucana komunikatem, że nie ma ich - w tej wersji, zamiast być po cichu ignorowana. Binarki nie są podpisane. + w tej wersji, zamiast być po cichu ignorowana. Pliki dla Linuksa nie są podpisane.

    Znalazłeś błąd albo potrzebujesz formatu? Zgłoszenia są otwarte, diff --git a/web/public/pl/plik-o-zadanym-rozmiarze/index.html b/web/public/pl/plik-o-zadanym-rozmiarze/index.html index e40be3b..3e5c9b9 100644 --- a/web/public/pl/plik-o-zadanym-rozmiarze/index.html +++ b/web/public/pl/plik-o-zadanym-rozmiarze/index.html @@ -198,7 +198,7 @@

    Prawdziwy plik tego formatu, o dokładnie zamówionym rozmiarze

    Pobierz 0.2.0 Zobacz kod źródłowy -

    Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Binarki nie są jeszcze podpisane, więc system ostrzeże przy pierwszym uruchomieniu - nota wydania mówi dokładnie, czego się spodziewać.

    +

    Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Pliki dla Windows i macOS są podpisane i uruchamiają się bez ostrzeżenia. Pliki dla Linuksa nie są podpisane.

    diff --git a/web/public/pl/zastosowania/index.html b/web/public/pl/zastosowania/index.html index f1114be..119925a 100644 --- a/web/public/pl/zastosowania/index.html +++ b/web/public/pl/zastosowania/index.html @@ -191,7 +191,7 @@

    Dla kogo to jest

    Pobierz 0.2.0 Zobacz kod źródłowy -

    Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Binarki nie są jeszcze podpisane, więc system ostrzeże przy pierwszym uruchomieniu - nota wydania mówi dokładnie, czego się spodziewać.

    +

    Darmowe i otwarte, GPL-3.0. Bez zakładania konta. Pliki dla Windows i macOS są podpisane i uruchamiają się bez ostrzeżenia. Pliki dla Linuksa nie są podpisane.

diff --git a/web/public/social.html b/web/public/social.html index 0e17082..da40984 100644 --- a/web/public/social.html +++ b/web/public/social.html @@ -210,12 +210,12 @@

Real test files.
At any exact size.

- 20 formats that open in the software that owns them, plus a manifest + 21 formats that open in the software that owns them, plus a manifest saying how your system should react to each file.

  • exact to the byte
  • -
  • 20 real formats
  • +
  • 21 real formats
  • same bytes every run
  • GUI + CLI
  • built for CI
  • diff --git a/web/public/use-cases/index.html b/web/public/use-cases/index.html index 144adcb..b621148 100644 --- a/web/public/use-cases/index.html +++ b/web/public/use-cases/index.html @@ -192,7 +192,7 @@

    Who this is for

    Download 0.2.0 View the source -

    Free and open source, GPL-3.0. Nothing to sign up for. The binaries are not signed yet, so your system will warn you the first time - the release notes say what to expect.

    +

    Free and open source, GPL-3.0. Nothing to sign up for. The Windows and macOS downloads are signed and start without a warning. The Linux ones are unsigned.