Windows Docker: Tips and Tricks for List & Label

To successfully execute List & Label within a Windows Docker image, there are several valuable tips and tricks as well as important considerations that must be taken into account.

Note: The subsequent advice and strategies pertain to the utilization of a .NET application but are applicable, irrespective of this, to other programming environments such as C++, VCL, etc.

Printerless: No printer(-driver) available

In Windows Docker images, a usable printer driver is absent, necessitating that List & Label operate in the so-called Printerless mode - see also Using List & Label without a printer driver (Printerless). This fact is usually recognised automatically by List & Label under Docker and the mode is set accordingly. However, it can also be activated explicitly via the Printerless property:

...
LL.Printerless = true;
...

Printerless mode can cause slight deviations in the alignment and positioning of texts - see also Printerless mode: effects and options for text output. The LlOption.VirtualDeviceScalingOptions option can be used to influence the text output - example:

...
LL.Core.LlSetOption(LlOption.VirtualDeviceScalingOptions, 600);
...

Important: In this context, please note the point Missing Fonts in Windows Docker images in this article.

Logging

As the Debwin4 tool is not available in a Docker image to record the log outputs of List & Label as conveniently as in a simple Windows desktop application, the Logging must be redirected. The two properties Debug and DebugLogFilePath are available for this purpose:

...
LL.DebugLogFilePath = logfilePath;
LL.Debug = LlDebug.Enabled | LlDebug.LogToFile;
...

Additional critical debugging flags may include LlDebug.ObjectStates and LlDebug.ForceSysinfo.

Missing Fonts in Windows Docker Images

There are only a few Windows Docker images that include the usual array of fonts, which can be utilized for output. In Server-Core images, typically only the Lucon font type is available. Consequently, this limitation hinders the satisfactory display of outputs in reports that require fonts such as Verdana, Tahoma, Segoe UI, etc. For further details, see Installing Missing Fonts in Windows Server Core Docker Images.

With the release of List & Label version 30, fonts can now be directly embedded into project files via the designer, eliminating the need for subsequent font installations on the Docker image. However, earlier versions of List & Label must follow the procedure described here.

For earlier versions of List & Label, or when fonts are not embedded within the project files, it is crucial that the missing fonts are not merely provided on the Docker image by simple copying - excerpt from the Dockerfile:

...
WORKDIR /src
#Copy the fonts to docker
COPY ["/fonts/", "/fonts/"]
...

The copied fonts must also be communicated to the executing process before the List & Label object is created. This can be implemented at runtime via the Windows API AddFontResource():

internal static class NativeMethods
{
    [DllImport("gdi32.dll")]
    internal static extern int AddFontResource
        (
        string lpFilename
        );
}

// ...

string[] files = Directory.GetFiles(Path.Combine(appPath, "Fonts"));
foreach(string fileName in files)
{
    NativeMethods.AddFontResource(fileName);
}

Note: When copying and deploying fonts in the Docker image, please consider the relevant licensing terms of the manufacturer for the fonts.

Mixed Measurement Systems

The print templates are initially created on a local Windows system using either the Designer or the Web Report Designer, taking into account the regional settings of the local Windows environment. However, the provided Windows Docker images are all based on en-US, resulting in the use of inches as the measurement system.

If the local Windows system used for creating print templates in the Designer operates on a de-DE system and utilizes millimeters as the unit of measurement, and if the template is then employed in a Docker image with an en-US setting, unexpected infinite loops and erroneous displays may occur in the report. This discrepancy arises because the en-US standard calculates using inches instead of millimeters - for further details, refer to Use of List & Label Print Templates Under Mixed Measurement Systems. Consequently, it is advisable to use the Designer function UnitFromSCM for specifying positions and dimensions in formulas within the Designer. Through the Unit property, the measurement system of List & Label can be determined:

...
LL.Unit = LlUnits.Millimeter_1_1000;
...

A basic structure in .NET:

In summary, with the information provided above, the following code snippet serves as an excellent starting point:

internal static class NativeMethods
{
    [DllImport("gdi32.dll")]
    internal static extern int AddFontResource
        (
        string lpFilename
        );
}

// ...

// installing fonts to current process
string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string[] files = Directory.GetFiles(Path.Combine(appPath, "Fonts"));
foreach (string fileName in files)
    NativeMethods.AddFontResource(fileName);

using (ListLabel LL = new ListLabel())
{
    //LL.LicensingInfo = "";

    // define printerless-mode
    LL.Printerless = true;

    // define scaling value for text rendering
    LL.Core.LlSetOption(LlOption.VirtualDeviceScalingOptions, 600);

    // define path for logfile and delete old one
    string logfilePath = Path.Combine(appPath, "debug.log");
    if (File.Exists(logfilePath))
        File.Delete(logfilePath);

    // enable debnugging into logfile with helpful options
    LL.DebugLogFilePath = logfilePath;
    LL.Debug = LlDebug.Enabled | LlDebug.LogToFile | 
        LlDebug.ObjectStates | LlDebug.ForceSysinfo;

    // define the measurement system
    LL.Unit = LlUnits.Millimeter_1_1000;

    // define additional options, the dada source,
    // project-file/repository, export parameters etc.
    string profectFile = Path.Combine(appPath, "myProject.lst");
    string exportFile = Path.Combine(appPath, "export.ll");

    ExportConfiguration exportPreview = 
        new ExportConfiguration(LlExportTarget.Preview, exportFile, profectFile);

    LL.Export(exportPreview);
}
...