Export to PDF

This tutorial outlines how to create a PDF export of the WellLog widget. The method exportToPdf simplifies the PDF format export in the web browser of the visible or scrollable limits of the widget. Data must be loaded to memory before export.

In addition, the exported document can have a page footer and header and a document footer and header (head and tail). By default, page and document footers and headers are not specified. Head and tail can be described in a declarative way using the geotoolkit/report library in the XML-based document, or if necessary, create a custom header using GeoToolkit shapes directly. In this tutorial, all options are demonstrated, beginning with exporting without headers.

# Export to PDF without headers and footers

This example exports whole limits of the tracks to PDF without headers and footers.

The example fits all tracks to the page width using the scaling option and exports in continuous mode. The user has the option to select a customs track scale in PDF or preserve the same scale as on-screen. Also, the user can change the exported file name in the Save As option by entering the preferred file name.

import { ExportToPdf } from "/src/code/WellLog/AdditionalFunctionality/ExportToPDF/exportToPdf.ts";
function createScene(canvas) {
  return new ExportToPdf(canvas, false);
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

This section of the tutorial exports whole limits of the tracks to PDF and uses a custom document header and footer component. The position of each element is defined as relative coordinates of the header and footer. The header and footer use a simple parser to create shapes from a JSON-based definition of the elements.

In addition to document headers and footers, each page of the PDF can also contain a header and footer. The example below uses the same WellLogWidget as the previous examples with the page headers and footers included. Note that you need to disable continuous mode.

# Export to PDF Using Templates

To simplify creation of complex reports, GeoToolkit provides a small library geotoolkit/report to describe, parse, and represent the document footer and header in declarative form using an XML-based format. These documents follow XML schema [schema.xsd]. The following code demonstrates loading templates using AJAX. These XML templates are processed with the Underscore.js library to create an XML document from the provided templates. This library uses JSON objects to represent a model of parameters such as company name. Prepared documents are then parsed with geotoolkit/report to be represented with document headers and footers. To see a new report, click the toolbar Print icon.

# Export to PDF Using Templates with Page Breaks

This section shows how to modify templates to support page breaks.
To set a page break you need to specify style="page-break:before|after" attribute or page-break="before|after"

# Export to PDF with WellLog Page Header

This section shows how to create an example of page header, which display WellLog Header on page. Code below shows how to initialize WellLogHeaderComponent.

Close

# Export to PDF with Custom Font

This section of the tutorial exports whole limits of the tracks to PDF and uses a custom font to display the header's text. TrueType fonts could be embedded into the PDF document for the correct viewing of the document on all devices. This example has an embedded font in embededfonts.js file. To use another font, convert the font to the base64 format.

import { CustomExportToPDF } from "/src/code/WellLog/AdditionalFunctionality/ExportToPDF/exportToPdf.ts";
function createScene(canvas) {
  return new CustomExportToPDF(canvas, false);
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Export to PDF via NodeJS

This section provides a simple demonstration of how to export WellLog widget to PDF via NodeJS.
To do this:

  • Go to the the nodejs folder and run npm install. Check to ensure node.js is installed. Supported version of node.js can be found in package.json.
  • Run npm start

The file server-node-export-pdf.ts contains a standard initialization for ReportJS, loading custom fonts if necessary and export to PDF. The full source code can be found in examples/vue/tutorials/src/code/WellLog/AdditionalFunctionality/ExportToPDF/nodejs folder

In source code:

# Load environment settings for node.js

Load environment settings for node.js

import '@int/geotoolkit/environment';

Load templates of the header and the footer from xml. There are in Report.JS format.

fs.readFile('../templates/template-header.xml', (err, header) => {
  fs.readFile('../templates/template-footer.xml', (err, footer) => {
      ...
  }
}

# Pre-process data

Pre-process templates with lodash to apply data to templates.

const headerTemplate = lodash.template(header.toString())(data);
const footerTemplate = lodash.template(footer.toString())(data);

# Parse template to reports

Parse template using Report.JS to GeoToolkit and use parsed header as documentheader and documentfooter.

const headerParser = new Parser(headerTemplate);
const footerParser = new Parser(footerTemplate);
Promise.all([
    headerParser.parse(),
    footerParser.parse()
]).then((templates) => {
  ...
});

# Source Code

The source code of server-node-export-pdf.ts and stream implementation for node.js stream.ts


    import fs from 'fs';
    import lodash from 'lodash-es';

    import '@int/geotoolkit/environment';
    import {obfuscate} from '@int/geotoolkit/lib';
    import {FontSubType} from '@int/geotoolkit/pdf/FontSubType';
    import {Orientation} from '@int/geotoolkit/util/Orientation';
    import {PaperOrientation} from '@int/geotoolkit/scene/exports/PaperOrientation';
    import {ScalingOptions} from '@int/geotoolkit/scene/exports/ScalingOptions';
    import {PaperFormatFactory} from '@int/geotoolkit/scene/exports/PaperFormatFactory';
    import {UnitFactory} from '@int/geotoolkit/util/UnitFactory';
    import {Parser} from '@int/geotoolkit/report/Parser';
    import {log} from '@int/geotoolkit/base';
    import {CssLayout} from '@int/geotoolkit/layout/CssLayout';
    import {Group} from '@int/geotoolkit/scene/Group';
    import {Plot} from '@int/geotoolkit/plot/Plot';
    import {Log2DDataRow} from '@int/geotoolkit/welllog/data/Log2DDataRow';
    import {Log2DVisualData} from '@int/geotoolkit/welllog/data/Log2DVisualData';
    import {DefaultColorProvider} from '@int/geotoolkit/util/DefaultColorProvider';
    import {Log2DVisual, PlotTypes} from '@int/geotoolkit/welllog/Log2DVisual';
    import {TrackType} from '@int/geotoolkit/welllog/TrackType';
    import {createCanvasElement} from '@int/geotoolkit/dom';
    import {CompositeLogCurve} from '@int/geotoolkit/welllog/CompositeLogCurve';
    import {AdaptiveLogCurveVisualHeader} from '@int/geotoolkit/welllog/header/AdaptiveLogCurveVisualHeader';
    import {HeaderType, LogAxisVisualHeader} from '@int/geotoolkit/welllog/header/LogAxisVisualHeader';
    import {LogAxis} from '@int/geotoolkit/welllog/LogAxis';
    import {AlignmentStyle, BaseLineStyle, TextStyle} from '@int/geotoolkit/attributes/TextStyle';
    import {WellLogWidget} from '@int/geotoolkit/welllog/widgets/WellLogWidget';
    import {MathUtil} from '@int/geotoolkit/util/MathUtil';
    import {LogCurve} from '@int/geotoolkit/welllog/LogCurve';

    import {NodeStream} from './utils/stream';
    import {DataSource} from './datasource';
    import {Node} from '@int/geotoolkit/scene/Node';
    import {DataBinding} from '@int/geotoolkit/data/DataBinding';
    import {DataBindingRegistry} from '@int/geotoolkit/data/DataBindingRegistry';
    import {LogCurveDataSource} from '@int/geotoolkit/welllog/data/LogCurveDataSource';
    import {AbstractUnit} from '@int/geotoolkit/util/AbstractUnit';
    import {AbstractPaperFormat} from '@int/geotoolkit/scene/exports/AbstractPaperFormat';
    import {PdfExport} from '@int/geotoolkit/pdf/PdfExport';

    const barCodePngPath = '../templates/assets/images/bar-code.png';
    const companyLogoPath = '../templates/assets/images/company-logo.png';
    const robotoFontPath = '../../../../../assets/fonts/roboto.ttf';
    class BindingFunction extends DataBinding {
        accept (node: Node) {
            return node instanceof LogCurve;
        }
        bind (curve: CompositeLogCurve, data: any) {
            const info = curve.getId();
            if (curve.getDataSource() != null) {
                curve.getDataSource().clear();
            }
            const source = data.getCurveSource(info['id']);
            if (source != null) {
                curve.setData(source, true, true);
                const limits = MathUtil.calculateNeatLimits(source.getMinValue(), source.getMaxValue());
                curve.setNormalizationLimits(limits.getLow(), limits.getHigh());
            }
        }
        unbind (node: Node, data?: any): void {
        }
    }
    obfuscate(BindingFunction);


    let plot: Plot | null = null,
        data = null;
    const widget = initializeScene();
    printToPdf(widget);

    function createWidget (data: DataSource) {
        const range = data.getIndexRange();
        const widget = new WellLogWidget({
            'viewcache': false,
            'range': range,
            'indent': 0,
            'header': {
                'visible': true
            },
            'footer': {
                'visible': false
            },
            'trackcontainer': {
                'border': {'visible': true}
            },
            'border': {'visible': true},
            'indextype': 'depth',
            'indexunit': 'm',
            'deviceunit': 'cm'
        });
        widget.setLayoutStyle({'left': 0, 'top': 0, 'right': 0, 'bottom': 0});

        configureHeaders(widget);

        // Add data binding for curve
        (widget.getDataBinding() as DataBindingRegistry).add(new BindingFunction());
        widget.setData(data);
        data.update();
        return widget;
    }

    function configureHeaders (widget: WellLogWidget) {
        const CUSTOM_TEXT_STYLE = new TextStyle('rgb(0,0,0)', BaseLineStyle.Middle, AlignmentStyle.Left, '10px etfo');
        const headerProvider = widget.getHeaderContainer().getHeaderProvider();

        // configure Depth ant Time axis header
        const logAxisVisualHeader = headerProvider.getHeaderProvider(LogAxis.getClassName()) as LogAxisVisualHeader;
        logAxisVisualHeader.setHeaderType(HeaderType.Simple);

        // configure curve header
        const header = new AdaptiveLogCurveVisualHeader();
        header.setElement('ScaleTo', {'anchor': AnchorType.RightCenter, 'section': 'top'});
        header.setElement('ScaleFrom', {'anchor': AnchorType.LeftCenter, 'section': 'top'});
        header.setElement('Line', {'anchor': AnchorType.Center, 'section': 'center'});
        header.setElement('Name', {'anchor': AnchorType.Center, 'section': 'top'});
        header.setElement('Unit', {'anchor': AnchorType.Center, 'section': 'bottom'});
        header.setElement('Tracking', {'anchor': AnchorType.Center, 'section': 'bottom'});

        header.setTextStyle(CUSTOM_TEXT_STYLE);
        headerProvider.registerHeaderProvider(CompositeLogCurve.getClassName(), header);
    }

    function initializeScene () {
        data = new DataSource();
        const wellLogWidget = createWidget(data);

        addDefaultTemplate(wellLogWidget, ['GR', 'CALI'], ['GR', 'Плотность'], ['#ef6c00', '#2196f3']);
        addLog2DData(wellLogWidget);

        wellLogWidget.setDepthLimits(4500, 5800);
        const canvas = createCanvasElement(800, 800);

        plot = addWidgetToCanvas(canvas, wellLogWidget);

        // Tools
        wellLogWidget.getToolByName('cross-hair')
            .setEnabled(false);
        wellLogWidget.invalidate();
        return wellLogWidget;
    }

    function addLog2DData (widget: WellLogWidget) {
        widget.addTrack(TrackType.LinearTrack)
            .addChild([
                create2DVisual(createLog2DTestData())
                    .setPlotType(PlotTypes.Linear)
            ]);
    }

    function create2DVisual (log2dData: Log2DVisualData): Log2DVisual {
        const visual = new Log2DVisual();
        visual.setData(log2dData);
        const colors = new DefaultColorProvider();

        const min = log2dData.getMinValue();
        const max = log2dData.getMaxValue();

        const delt = (max - min) / 4;
        colors.addColor(min, 'green');
        colors.addColor(min + delt, 'yellow');
        colors.addColor(min + 2 * delt, 'orange');
        colors.addColor(max - delt, 'blue');
        colors.addColor(max, 'red');
        visual.setColorProvider(colors);

        return visual;
    }

    function createLog2DTestData (): Log2DVisualData {
        const PI = Math.PI;
        const maxDepth = 5800;
        const minDepth = 4500;
        const depthStep = (maxDepth - minDepth) / 10;
        const log2dData = new Log2DVisualData();
        for (let i = 1; i < depthStep; i++) {
            const depth = minDepth + depthStep * i;
            const values = [0, 1, 2, 3, 4];

            const angles = [0, PI / 2, PI, PI * 3 / 2, PI * 2];
            log2dData.getRows().push(new Log2DDataRow(depth, values, angles));
        }
        log2dData.updateLimits();
        return log2dData;
    }

    function addWidgetToCanvas (canvasElement: HTMLCanvasElement, widget: WellLogWidget) {
        // create a basic plot
        const widgetPlot = new Plot({
            'canvaselement': canvasElement,
            'root': new Group()
                .setAutoModelLimitsMode(true)
                .setLayout(new CssLayout())
                .addChild(widget),
            'autosize': false,
            'autorootbounds': true
        });
        widgetPlot.setSize(800, 800);
        return widgetPlot;
    }

    function addDefaultTemplate (wellLogWidget: WellLogWidget, dataMnemonic: string[], curveMnemonic: string[], colors: string[]) {
        for (let i = 0; i < curveMnemonic.length; i++) {
            wellLogWidget.addTrack({
                type: TrackType.IndexTrack,
                width: i === 0 ? 70 : 50
            })
                .enableClipping(true);
            const track = wellLogWidget.addTrack(TrackType.LinearTrack)
                .enableClipping(true);
            const logCurve = new CompositeLogCurve();
            logCurve.setLineStyle({'color': colors[i], 'width': 2});
            logCurve.setId({'id': dataMnemonic[i], 'wellId': 'wellId'} as any);
            track.addChild(logCurve);
            const ds = logCurve.getDataSource() as LogCurveDataSource;
            ds.setData({
                'name': curveMnemonic[i],
                'depths': ds.getDepths(),
                'values': ds.getValues()
            });
        }
    }
    function base64EncodeImage (file: string): string {
        // read binary data
        const bitmap = fs.readFileSync(file, 'base64');
        return 'data:image/png;base64,' + bitmap;
    }
    function exportToPdf (widget: WellLogWidget) {
        // Compute default paper format
        const DOCUMENT_TITLE = 'Main Well';
        const barCodePng = base64EncodeImage(barCodePngPath);
        const companyLogo = base64EncodeImage(companyLogoPath);
        const robotoFont = fs.readFileSync(robotoFontPath, 'base64');

        const data = {
            Title: DOCUMENT_TITLE,
            CompanyName: 'Oil and Gas Company',
            Address: {
                Well: 'Oil and Gas main Well',
                Field: 'MajorCamp',
                County: 'Culberson',
                State: 'Texas',
                Country: 'USA'
            },
            Items: [
                {
                    Chanel: 'GR',
                    Source: 'SPULSE-GEN:SPULSE-GEN:SPEC',
                    Sampling: '6in - RT'
                },
                {
                    Chanel: 'ROP5',
                    Source: 'DRILLING_SURFACE',
                    Sampling: '6in - RT'
                },
                {
                    Chanel: 'GR',
                    Source: 'SPULSE-GEN:SPULSE-GEN:SPEC',
                    Sampling: '6in - RT'
                }
            ],
            Parameters: [
                {
                    Name: 'BS',
                    Description: 'Bit Size',
                    Tool: 'DNMSESSION',
                    Value: '6.125',
                    Unit: 'in'
                },
                {
                    Name: 'DFD',
                    Description: 'Drilling Fluid Density',
                    Tool: 'Borehole',
                    Value: '10',
                    Unit: 'lbm/gal'
                }
            ],
            Images: {
                BarCode: barCodePng,
                CompanyLogo: companyLogo
            }
        };
        const onFail = function (reason: string) {
            log('Error : ' + reason);
        };
        fs.readFile('../templates/template-header.xml', (err, header) => {
            fs.readFile('../templates/template-footer.xml', (err, footer) => {
                const headerTemplate = lodash.template(header.toString())(data);
                const footerTemplate = lodash.template(footer.toString())(data);
                const headerParser = new Parser(headerTemplate);
                const footerParser = new Parser(footerTemplate);
                Promise.all([
                    headerParser.parse(),
                    footerParser.parse()
                ]).then((templates) => {
                    const limits = widget.getDepthLimits();

                    const unitFactory = UnitFactory.getInstance();
                    const px = unitFactory.getUnit('px');

                    const printSettings: PdfExport.DocumentExportSettings = {
                        'paperformat': PaperFormatFactory.getInstance().getPaper('A4', px as AbstractUnit,
                            PaperOrientation.Portrait) as AbstractPaperFormat,
                        'scaling': ScalingOptions.FitWidth,
                        'keepaspectratio': false,
                        'continuous': true,
                        'top': 0.5,
                        'bottom': 0.5,
                        'left': 0.5,
                        'right': 0.5,
                        'units': 'cm'
                    };
                    const printUnit = unitFactory.getUnit(printSettings['units'] as string) as AbstractUnit;
                    const marginL = Math.floor(printUnit.convert(printSettings['left'] as number, px as AbstractUnit));
                    const marginR = Math.floor(printUnit.convert(printSettings['right'] as number, px as AbstractUnit));
                    const marginT = Math.floor(printUnit.convert(printSettings['top'] as number, px as AbstractUnit));
                    const marginB = Math.floor(printUnit.convert(printSettings['bottom'] as number, px as AbstractUnit));
                    const paperInPixels = printSettings['paperformat'] as AbstractPaperFormat;
                    const paperW = Math.floor(paperInPixels.getWidth());
                    const paperH = Math.floor(paperInPixels.getHeight());
                    const templateWidth = widget.getOrientation() === Orientation.Vertical ?
                        paperW - marginL - marginR :
                        paperH - marginT - marginB;
                    const headerElement = templates[0] ? templates[0].getRootElement(templateWidth) : null;
                    const footerElement = templates[1] ? templates[1].getRootElement(templateWidth) : null;
                    const stream = new NodeStream('pdfexport-result.pdf');

                    const pdfOptions: WellLogWidget.ExportToPdfOptions = {
                        'output': 'Widget',
                        'printsettings': printSettings,
                        'deviceunit': widget.getDeviceUnit(),
                        'indexunit': widget.getIndexUnit(),
                        'scale': widget.getDepthScale(),
                        'limits': {
                            'start': limits.getLow(),
                            'end': limits.getHigh()
                        },
                        'pdfstream': stream,
                        'documentheader': headerElement as any, // temporary
                        'documentfooter': footerElement as any,
                        'embededfonts': [
                            {
                                subtype: FontSubType.TrueType,
                                fontname: 'roboto',
                                fontweight: 'normal',
                                fontstyle: 'normal',
                                fontbase64encodedfile: robotoFont,
                                encoding: 'Identity-H'
                            }
                        ]
                    };
                    widget.exportToPdf(pdfOptions).then(() => {
                        stream.close();
                        end(widget);
                    });
                }, (reason) => {
                    onFail(reason);
                });
            });
        });
    }

    function printToPdf (widget: WellLogWidget) {
        exportToPdf(widget);
    }

    function end (widget: WellLogWidget) {
        if (plot) plot.dispose();
        process.exit();
    }

Close