* * @see http://wcpos.com * @package WCPOS\WooCommercePOS */ namespace WCPOS\WooCommercePOS\Templates\Thermal; /** * Thermal_Bounds class. */ final class Thermal_Bounds { /** * Narrowest receipt in character columns, for every lane. * * Deliberately 1, not the 16 the PDF page uses. This bound is applied by the * PARSER, so it reaches every emitter — including Text_Thermal_Emitter, which * renders plain text with no physical roll behind it and has always accepted * narrow widths (`paper-width="10"` is pinned by its own tests). Raising the * shared floor to 16 silently re-centred that output. * * The rule this PR is built on is that a bound holds on every lane or none. * A floor of 16 is real, but it belongs to the medium that has a page, not to * the markup — so it stays where it already was ({@see self::PAPER_WIDTH_PDF_MIN}). * The hazard this PR exists to close is the top end, not the bottom. */ public const PAPER_WIDTH_MIN = 1; /** * Narrowest receipt the PDF/preview page will lay out. * * Pre-existing behaviour of Html_Thermal_Emitter, preserved rather than * generalised: below this its character-cell arithmetic has nothing to divide. */ public const PAPER_WIDTH_PDF_MIN = 16; /** * Widest receipt in character columns. * * 120 covers the widest thermal and impact rolls sold; the character grid * stops being a receipt beyond it. */ public const PAPER_WIDTH_MAX = 120; /** * Smallest text size multiplier (normal size). */ public const SIZE_MULTIPLIER_MIN = 1; /** * Largest text size multiplier. * * The ESC/POS `GS ! n` size byte carries one nibble per axis, so 8x is the * ceiling the hardware can express; Star's magnification tops out lower * still. Html_Thermal_Emitter renders the same 8em maximum. */ public const SIZE_MULTIPLIER_MAX = 8; /** * Shortest 1D barcode, in dots. */ public const BARCODE_HEIGHT_MIN = 1; /** * Tallest 1D barcode, in dots. * * `GS h n` is a single byte. */ public const BARCODE_HEIGHT_MAX = 255; /** * Smallest QR module size. */ public const QRCODE_SIZE_MIN = 1; /** * Largest QR module size. * * The ESC/POS `GS ( k` module-size function accepts 1-16. */ public const QRCODE_SIZE_MAX = 16; /** * Narrowest image, in printer dots. */ public const IMAGE_WIDTH_DOTS_MIN = 1; /** * Widest image, in printer dots. * * Comfortably past the 576-dot budget of an 80mm head, so it never truncates * a real logo, while still bounding the em width handed to Dompdf. */ public const IMAGE_WIDTH_DOTS_MAX = 2000; /** * Fewest lines a `` advances. */ public const FEED_LINES_MIN = 1; /** * Most lines a `` advances. * * At roughly 3.5mm per line this is ~17cm of blank paper — already far more * than any tear-off or cut gap a template legitimately wants, so a larger * number is a typo, and an unbounded one is a hazard: every wire emitter * turns `lines` straight into a loop or a str_repeat(). This is also the * bound the PDF path has shipped since it was written, so pinning the rest * of the paths to 50 moves the fewest of them. */ public const FEED_LINES_MAX = 50; /** * Narrowest fixed column, in characters. * * A zero-width column would silently delete a semantic cell from the row. */ public const COL_WIDTH_MIN = 1; /** * Widest fixed column, in characters. * * A column cannot outgrow the widest paper. */ public const COL_WIDTH_MAX = self::PAPER_WIDTH_MAX; /** * Printable dots across 80 mm paper at 203 dpi. */ public const DOTS_80MM = 576; /** * Printable dots across 58 mm paper at 203 dpi. */ public const DOTS_58MM = 384; /** * Column count at or above which paper is treated as 80 mm. * * 58 mm rolls carry 32 columns; 80 mm rolls carry 42 or 48. */ public const WIDE_PAPER_COLUMNS = 40; /** * The printable width of the roll, in dots. * * Templates size images in dots, but declare paper in character columns, so * every lane that puts an image on paper has to bridge the two. Shared here * because an image scaled against 576 dots on one lane and 384 on another is * the same divergence this whole table exists to prevent. * * @param int $columns The paper width in character columns. * * @return int The printable width in dots. */ public static function paper_dots( int $columns ): int { return $columns >= self::WIDE_PAPER_COLUMNS ? self::DOTS_80MM : self::DOTS_58MM; } /** * Clamp a value into one of the ranges above. * * Thermal_Markup_Parser already clamps every attribute on its way into the * AST, so for parsed templates this is a no-op. It is here for ASTs built by * hand — tests, and any future caller that skips the parser — because a * bound the emitters do not enforce themselves is a bound that stops holding * the moment someone builds a node directly. * * @param mixed $value The candidate value. * @param int $fallback The fallback for missing/non-numeric values. * @param int $min The lowest legal value. * @param int $max The highest legal value. * * @return int The clamped integer. */ public static function clamp_int( $value, int $fallback, int $min, int $max ): int { if ( ! is_numeric( $value ) ) { return $fallback; } return max( $min, min( $max, (int) $value ) ); } }