This example uses OpenLayers v10.9.0. The latest is v.
Edit

#### Print to scale example

print 1 printing 1 scale 4 scaleline 2 export 3 pdf 2 grid 8

#####

Page size
Resolution
Scale

Example of printing a map to a specified scale.

Example of printing a map to a specified scale. The print is exported as a PDF using the jsPDF library. Unlike the Export PDF example the on screen map is only used to set the center and rotation. The extent printed depends on the scale and page size. To print the scale bar and attributions the example uses the html2canvas library.

##### main.js

    import html2canvas from 'html2canvas';
    import {jsPDF} from 'jspdf';
    import Map from 'ol/Map.js';
    import View from 'ol/View.js';
    import ScaleLine from 'ol/control/ScaleLine.js';
    import {defaults as defaultControls} from 'ol/control/defaults.js';
    import WMTSCapabilities from 'ol/format/WMTSCapabilities.js';
    import TileLayer from 'ol/layer/Tile.js';
    import {getPointResolution, get as getProjection} from 'ol/proj.js';
    import {register} from 'ol/proj/proj4.js';
    import WMTS, {optionsFromCapabilities} from 'ol/source/WMTS.js';
    import proj4 from 'proj4';

    proj4.defs(
      'EPSG:27700',
      '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 ' +
        '+x_0=400000 +y_0=-100000 +ellps=airy ' +
        '+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 ' +
        '+units=m +no_defs',
    );

    register(proj4);

    const proj27700 = getProjection('EPSG:27700');
    proj27700.setExtent([0, 0, 700000, 1300000]);

    const raster = new TileLayer();

    const url =
      'https://tiles.arcgis.com/tiles/qHLhLQrcvEnxjtPr/arcgis/rest/services/OS_Open_Raster/MapServer/WMTS';
    fetch(url)
      .then(function (response) {
        return response.text();
      })
      .then(function (text) {
        const result = new WMTSCapabilities().read(text);
        const options = optionsFromCapabilities(result, {
          layer: 'OS_Open_Raster',
        });
        options.attributions =
          'Contains OS data © Crown Copyright and database right ' +
          new Date().getFullYear();
        options.crossOrigin = '';
        options.projection = proj27700;
        options.wrapX = false;
        raster.setSource(new WMTS(options));
      });

    const map = new Map({
      layers: [raster],
      controls: defaultControls({
        attributionOptions: {collapsible: false},
      }),
      target: 'map',
      view: new View({
        center: [373500, 436500],
        projection: proj27700,
        zoom: 7,
      }),
    });

    const scaleLine = new ScaleLine({bar: true, text: true, minWidth: 125});
    map.addControl(scaleLine);

    const dims = {
      a0: [1189, 841],
      a1: [841, 594],
      a2: [594, 420],
      a3: [420, 297],
      a4: [297, 210],
      a5: [210, 148],
    };

    // export options for html2canvase.
    // See: https://html2canvas.hertzen.com/configuration
    const exportOptions = {
      useCORS: true,
      ignoreElements: function (element) {
        const className = element.className || '';
        return (
          className.includes('ol-control') &&
          !className.includes('ol-scale') &&
          (!className.includes('ol-attribution') ||
            !className.includes('ol-uncollapsible'))
        );
      },
    };

    const exportButton = document.getElementById('export-pdf');

    exportButton.addEventListener(
      'click',
      function () {
        exportButton.disabled = true;
        document.body.style.cursor = 'progress';

        const format = document.getElementById('format').value;
        const resolution = document.getElementById('resolution').value;
        const scale = document.getElementById('scale').value;
        const dim = dims[format];
        const width = Math.round((dim[0] * resolution) / 25.4);
        const height = Math.round((dim[1] * resolution) / 25.4);
        const viewResolution = map.getView().getResolution();
        const scaleResolution =
          scale /
          getPointResolution(
            map.getView().getProjection(),
            resolution / 25.4,
            map.getView().getCenter(),
          );

        map.once('rendercomplete', function () {
          exportOptions.width = width;
          exportOptions.height = height;
          html2canvas(map.getViewport(), exportOptions).then(function (canvas) {
            const pdf = new jsPDF('landscape', undefined, format);
            pdf.addImage(
              canvas.toDataURL('image/jpeg'),
              'JPEG',
              0,
              0,
              dim[0],
              dim[1],
            );
            pdf.save('map.pdf');
            // Reset original map size
            scaleLine.setDpi(undefined);
            map.getTargetElement().style.width = '';
            map.getTargetElement().style.height = '';
            map.updateSize();
            map.getView().setResolution(viewResolution);
            exportButton.disabled = false;
            document.body.style.cursor = 'auto';
          });
        });

        // Set print size
        scaleLine.setDpi(resolution);
        map.getTargetElement().style.width = width + 'px';
        map.getTargetElement().style.height = height + 'px';
        map.updateSize();
        map.getView().setResolution(scaleResolution);
      },
      false,
    );

##### index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>Print to scale example</title>
        <link rel="stylesheet" href="node_modules/ol/ol.css">
        <style>
          .map {
            width: 100%;
            height: 400px;
          }
          .wrapper {
            max-width: 566px;
            width: 100%;
            height: 400px;
            overflow: hidden;
            margin-bottom: 10px;
          }
          #export-pdf {
            margin-top: 10px;
          }
        </style>
      </head>
      <body>
        <div class="wrapper">
          <div id="map" class="map"></div>
        </div>
        <form class="form">
          <label for="format">Page size </label>
          <select id="format">
            <option value="a0">A0 (slow)</option>
            <option value="a1">A1</option>
            <option value="a2">A2</option>
            <option value="a3">A3</option>
            <option value="a4" selected>A4</option>
            <option value="a5">A5 (fast)</option>
          </select>
          <label for="resolution">Resolution </label>
          <select id="resolution">
            <option value="72">72 dpi (fast)</option>
            <option value="150">150 dpi</option>
            <option value="200" selected>200 dpi</option>
            <option value="300">300 dpi (slow)</option>
          </select>
          <label for="scale">Scale </label>
          <select id="scale">
            <option value="500">1:500000</option>
            <option value="250" selected>1:250000</option>
            <option value="100">1:100000</option>
            <option value="50">1:50000</option>
            <option value="25">1:25000</option>
            <option value="10">1:10000</option>
          </select>
        </form>
        <button id="export-pdf">Export PDF</button>

        <script type="module" src="main.js"></script>
      </body>
    </html>

##### package.json

    {
      "name": "print-to-scale",
      "dependencies": {
        "ol": "10.9.0",
        "html2canvas": "^1.4.1",
        "jspdf": "^4.0.0",
        "proj4": "2.20.8"
      },
      "devDependencies": {
        "vite": "^3.2.3"
      },
      "scripts": {
        "start": "vite",
        "build": "vite build"
      }
    }