1
  1. I am working on a React project where I use the react-map-gl and @deck.gl/react libraries to display a map along with DeckGL layers on the screen.
  2. I have added PDF printing functionality to the project, utilizing the jsPDF library for this purpose.
  3. The printing functionality includes options such as page size and orientation.
  4. However, there is a problem: the PDF output only captures the visible portion of the map that is currently displayed on the screen. For example, if I select an A3 page size in landscape orientation, the screenshot includes only the visible map area, while the rest of the page is filled with whitespace.
  5. Below is my code for the map and print components.

Map component:

import * as React from "react";
import ReactMap, { MapRef } from "react-map-gl";
import DeckGL from "@deck.gl/react/typed";
import useEffect from "react";
import { useDispatch } from "react-redux";
import { setMapRef, setDeckRef } from "@/store/features/map/mapSlice";

export default function Map(props: MapProps) {

    const mapRef = useRef<MapRef | null>(null);
    const deckRef = useRef(null);
    useEffect(() => {
        if (mapRef.current) {
            dispatch(setMapRef(mapRef.current)); // Pass the `current` value only
        }
        if (deckRef.current) {
            dispatch(setDeckRef(deckRef.current)); // Pass the `current` value only
        }
    }, [mapRef.current, deckRef.current, dispatch]);
    const layers = [...]
    return (
        <DeckGL
            ref={deckRef}
            useDevicePixels={true} 
            glOptions={{ preserveDrawingBuffer: true }}
            layers={layers}
        >
            <ReactMap
                ref={mapRef}
                mapboxAccessToken={import.meta.env.VITE_MAPBOX_ACCESS_TOKEN}
                mapLib={import("mapbox-gl")}
                reuseMaps
                mapStyle={getMapboxMapStyle(theme.palette.mode === "dark", props.style)}
                projection={{ name: "mercator" }}
                onLoad={onRender}
                preserveDrawingBuffer={true}
            />
        </DeckGL>
    );
}

PrintMap.tsx component:

import { Button, Paper, PaperProps, Stack, Typography, FormControl, InputLabel, Select, MenuItem } from "@mui/material";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { jsPDF } from 'jspdf';
import { selectMapRef, selectDeckRef } from "@/store/features/map/selectors/selectDrawnShape";
import { useSelector } from "react-redux";
import { RootState } from "@/store";

export type PrintMapProps = {
    /**
     * Callback when user cancels the dialog
     */
    onCancel: () => void;
    /**
     * Additional styles
     */
    sx?: PaperProps["sx"];
};

export default function PrintMap(props: PrintMapProps) {
    const { t } = useTranslation();
    const pdfTitle = useSelector(
        (state: RootState) => state.cycles.currentCycle?.id,
    );

    const mapRef = useSelector(selectMapRef) as any;
    const deckRef = useSelector(selectDeckRef) as any;
    console.log("deckRef", deckRef)
    const [printOptions, setPrintOptions] = useState({
        pageSize: 'A4',
        orientation: 'landscape',
    });

    const handlePrintMap = async () => {
        const deck = (deckRef as any)?.deck;
        try {
            if (!deckRef || !mapRef || !mapRef.getMap()) {
                console.error("Map or DeckGL not ready");
                return;
            }
    
            const deckCanvas = deck.canvas;
            if (!document.body.contains(deckCanvas)) {
                console.error("DeckGL canvas is not attached to the document");
                return;
            }
    
            const mapCanvas = mapRef.getMap().getCanvas();
            
            const screenWidth = window.innerWidth;
            const screenHeight = window.innerHeight;
    
            const dpi = 96; // Pixels per inch (standard screen DPI)
            const mmToPx = (mm: number) => (mm / 25.4) * dpi; // Convert mm to pixels
    
            const pageSize = printOptions.pageSize;
            const isLandscape = printOptions.orientation === 'landscape';
    
            let paperWidthMM = 0;
            let paperHeightMM = 0;
    
            if (pageSize === 'A3') {
                paperWidthMM = isLandscape ? 420 : 297;
                paperHeightMM = isLandscape ? 297 : 420;
            } else if (pageSize === 'A4') {
                paperWidthMM = isLandscape ? 297 : 210;
                paperHeightMM = isLandscape ? 210 : 297;
            } else if (pageSize === 'A5') {
                paperWidthMM = isLandscape ? 210 : 148;
                paperHeightMM = isLandscape ? 148 : 210;
            } else if (pageSize === 'A6') {
                paperWidthMM = isLandscape ? 148 : 105;
                paperHeightMM = isLandscape ? 105 : 148;
            } else { // Default to A4
                paperWidthMM = isLandscape ? 297 : 210;
                paperHeightMM = isLandscape ? 210 : 297;
            }
            const customWidth = mmToPx(paperWidthMM);
            const customHeight = mmToPx(paperHeightMM);
            const startX = Math.max(0, (screenWidth - customWidth) / 2);
            const startY = Math.max(0, (screenHeight - customHeight) / 2);
            const combinedCanvas = document.createElement('canvas');
            combinedCanvas.width = customWidth;
            combinedCanvas.height = customHeight;
            const ctx = combinedCanvas.getContext('2d');
            if (ctx) {
                // Draw the specific region of mapCanvas
                ctx.drawImage(
                    mapCanvas, // Source canvas
                    startX, // Source X
                    startY, // Source Y
                    customWidth, // Source width
                    customHeight, // Source height
                    0, // Destination X
                    0, // Destination Y
                    customWidth, // Destination width
                    customHeight // Destination height
                );
                ctx.drawImage(
                    deckCanvas, // Source canvas
                    startX, // Source X
                    startY, // Source Y
                    customWidth, // Source width
                    customHeight, // Source height
                    0, // Destination X
                    0, // Destination Y
                    customWidth, // Destination width
                    customHeight // Destination height
                );
            } else {
                console.error("Failed to get 2D context");
                return;
            }
            const combinedImage = combinedCanvas.toDataURL('image/png');
            console.log("combinedImage", combinedImage);
    
            const pdf = new jsPDF({
                orientation: printOptions.orientation as 'landscape' | 'portrait',
                unit: 'mm',
                format: [paperWidthMM, paperHeightMM],
            });
    
            pdf.addImage(combinedImage, 'PNG', 0, 0, paperWidthMM, paperHeightMM, undefined, 'NONE');
    
            pdf.save(`${pdfTitle}`);
        } catch (error) {
            console.error("Error while printing:", error);
        }
    };
        
    return (
        <Paper
            elevation={3}
            sx={{
                p: 5,
                pb: 3,
                pt: 3,
                width: "28.5rem",
                borderRadius: 3,
                ...(props.sx ?? {}),
            }}
        >
            <Stack direction="column" gap={2} width="100%">
                <Typography variant="h1">{t("Print map")}</Typography>
                <Typography variant="body1">
                    {t("Set printing options as per your requirement")}
                </Typography>
                <Stack gap={2}>
                    <FormControl fullWidth>
                        <InputLabel>Page Size</InputLabel>
                        <Select
                            value={printOptions.pageSize}
                            label="Page Size"
                            onChange={(e) => setPrintOptions({ ...printOptions, pageSize: e.target.value })}
                        >
                            <MenuItem value="Letter">Letter</MenuItem>
                            <MenuItem value="A3">A3</MenuItem>
                            <MenuItem value="A4">A4</MenuItem>
                            <MenuItem value="A5">A5</MenuItem>
                            <MenuItem value="A6">A6</MenuItem>
                        </Select>
                    </FormControl>
                    <FormControl fullWidth>
                        <InputLabel>Orientation</InputLabel>
                        <Select
                            value={printOptions.orientation}
                            label="Orientation"
                            onChange={(e) => setPrintOptions({ ...printOptions, orientation: e.target.value })}
                        >
                            <MenuItem value="portrait">Portrait</MenuItem>
                            <MenuItem value="landscape">Landscape</MenuItem>
                        </Select>
                    </FormControl>
                </Stack>
                <Stack gap={1}>
                    <Button
                        variant="contained"
                        color="primary"
                        fullWidth
                        disableRipple
                        disableElevation
                        onClick={() => { handlePrintMap(), props.onCancel() }}
                    >
                        {t("Print")}
                    </Button>
                    <Button
                        variant="text"
                        color="secondary"
                        fullWidth
                        disableRipple
                        disableElevation
                        onClick={() => props.onCancel()}
                    >
                        {t("Cancel")}
                    </Button>
                </Stack>
            </Stack>
        </Paper>
    );
}

  1. Package.json file:
[
  "react-map-gl": "7.1.6",
  "@deck.gl/core": "8.9.35",
  "jspdf": "^2.5.2",  
]
  1. The screenshot shows the issue where the map captured in the PDF only includes the currently visible area, leaving blank spaces where the offscreen parts of the map should be. This highlights the challenge of capturing a complete map view in scenarios like A3 landscape printing.(https://i.sstatic.net/kE3aKV5b.png)

Additional info:

  1. I conducted some research and found suggestions to set preserveDrawingBuffer={true} in both DeckGL and ReactMap. However, this approach did not resolve the issue.
  2. I also tried other potential solutions, but none of them worked, and now I’m stuck on how to fix this problem.
  3. The remaining functionality works correctly without any issues.
  4. The printing functionality works as expected for A5, A6, and letter page sizes in both landscape and portrait orientations. However, it does not work as expected for larger page sizes like A2, A3, and A4.
0

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.