Measure your 3D printer's skew (XY, and XZ / YZ with the cube) from a scan or a phone photo, then correct it.
Print one flat test plate. Its 81 holes sit on an exact 12.5 mm grid. Skewcal finds the centre of every hole, so hole size, flow and elephant's foot don't affect the result.
Print at 100% / Actual size. Use the dark-window version for light-coloured plates. After printing, measure the two arrowed spans for exact scale.
Take at least two images and turn the plate 90° between them. No scanner or paper sheet is perfectly square. Turning the plate flips the sign of that error, so Skewcal can measure it and remove it.
JPEG scans work too if the file keeps its dpi. Otherwise enter the dpi by hand.
With paper, absolute scale depends on the sheet printing at exactly 100%. Enter your measured spans for exact scale. A tablet screen needs no measuring.
Snap the four walls off the cube at the corner tabs. Each wall is then measured exactly like the flat plate, with the scanner, the tablet or the paper sheet, lying flat.
Open this page on an iPad, lay it flat, and show the marker ring full screen. A screen's pixels are spaced exactly, so scale needs no ruler check, and glass is flatter than paper. The plate lies on the screen; its holes glow from the backlight.
Turn brightness up and auto-lock off, and don't zoom the page. Wipe the screen first: dust under the plate lifts it. A 100 mm bar is drawn beside the ring. For exact scale, measure it with calipers and enter the length when you measure. Models marked "calibrated" have been checked this way already.
Most printer firmware can't correct skew, so Skewcal corrects the G-code instead. It moves every XY coordinate by the inverse of the measured skew about the bed centre. Arcs are converted to short lines.
Every file you slice is corrected automatically. Needs Python 3, which macOS and Linux already have.
Turn off Printer Settings → General → Supports binary G-code. The script can only edit text G-code.
For other slicers, or to try it first. Pick a plain-text .gcode file. It's corrected in your browser and saved as a .zip.