Conversion issue from Aspose.Words document with DisplayBarcode to PDF

Hi,
I’m trying to convert a MS Word document with { DisplayBarcode "https://www.bitworks.net" QR \q 3 \h 2250 } into a PDF document using Aspose.Words (for .NET).
Word_with_DisplayBarcode.docx (17.6 KB)

First of all, I must admit, that I am disappointed, that the Aspose.Words product does NOT automatically render the corresponding QC-code during the generation of the PDF. In contrast, when using MS Word to

the QR-codes are automatically included in the output. And thus I’d expect the same behaviour from Aspose.Words, when saving the Aspose.Words.Document using Aspose.Words.Saving.PdfSaveOptions!

After reading a lot of forum discussions, I stumbled upon these instructions: How to Create BarCode|Aspose.Words for .NET and set the Aspose.Words.Document.FieldOptions.BarcodeGenerator accordingly. Currently I’m testing with this feature using the following code:

public void AsposeWords_DisplayBarCode()
{
    #region Setup

    var inputFileName = @".\Word_with_DisplayBarcode.docx";
    var outputDirectory = @".";

    Aspose.Words.License licWords = new Aspose.Words.License();
    licWords.SetLicense("Aspose.Total.lic");

    // Setting the Aspose.PDF.License probably is not necessary, as this does not change the output at all!
    Aspose.Pdf.License licPdf = new Aspose.Pdf.License();
    licPdf.SetLicense("Aspose.Total.lic");

    // Setting the Aspose.Barcode.License probably is not necessary, as this does not change the output at all!
    Aspose.BarCode.License licBarCode = new Aspose.BarCode.License();
    licBarCode.SetLicense("Aspose.Total.lic");

    #endregion Setup

    #region Test

    var now = DateTime.Now;
    foreach (var flagSaveAsPDF in new bool[] { false, true })
    {
        // Load a docx file
        var wordDocument = new Aspose.Words.Document(inputFileName);
        wordDocument.FieldOptions.FieldUpdateCultureSource = FieldUpdateCultureSource.FieldCode;
        wordDocument.FieldOptions.BarcodeGenerator = new Aspose_Words_BarcodeGenerator();

        // Save the corresponding Aspose.Words.Document - as docx or pdf:
        var outputFileName = Path.Combine(outputDirectory, $"{now.ToString("yyyy-MM-dd_hh-mm-ss")}_Word_with_DisplayBarcode_Output.{(flagSaveAsPDF ? "pdf" : "docx")}");
        if (!flagSaveAsPDF)
        {
            // Save as docx:
            // The resulting docx-file contains the image of the QR-code and thus works as expected!
            wordDocument.Save(outputFileName, Aspose.Words.SaveFormat.Docx);
        }
        else
        {
            // Save as PDF:
            // The resulting pdf file does not include the QR-code and displays an error message instead: "Error! Bar code generator is not set."
            var pdfSaveOptions = new Aspose.Words.Saving.PdfSaveOptions
            {
                // None of these options have any notable effect regarding the QR-code:
                EmbedFullFonts = wordDocument.FontInfos.EmbedTrueTypeFonts,
                //UpdateFields = true,
                //UpdateSdtContent = true,
            };
            wordDocument.Save(outputFileName, pdfSaveOptions);
        }
    }

    #endregion Test
}

/// <summary>
/// Source: https://docs.aspose.com/words/net/how-to-generate-a-custom-barcode-image-for-displaybarcode-field/
/// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-.NET.git.
/// </summary>
public class Aspose_Words_BarcodeGenerator : IBarcodeGenerator
{
    /// <summary>
    /// Converts barcode image height from Word units to Aspose.BarCode units.
    /// </summary>
    /// <param name="heightInTwipsString"></param>
    /// <returns></returns>
    private static float ConvertSymbolHeight(string heightInTwipsString)
    {
        // Input value is in 1/1440 inches (twips).
        int heightInTwips = TryParseInt(heightInTwipsString);

        if (heightInTwips == int.MinValue)
            throw new Exception("Error! Incorrect height - " + heightInTwipsString + ".");

        // Convert to mm.
        return (float)(heightInTwips * 25.4 / 1440);
    }

    /// <summary>
    /// Converts barcode image color from Word to Aspose.BarCode.
    /// </summary>
    /// <param name="inputColor"></param>
    /// <returns></returns>
    private static Color ConvertColor(string inputColor)
    {
        // Input should be from "0x000000" to "0xFFFFFF".
        int color = TryParseHex(inputColor.Replace("0x", ""));

        if (color == int.MinValue)
            throw new Exception("Error! Incorrect color - " + inputColor + ".");

        return Color.FromArgb(color >> 16, (color & 0xFF00) >> 8, color & 0xFF);

        // Backward conversion -
        // return string.Format("0x{0,6:X6}", mControl.ForeColor.ToArgb() & 0xFFFFFF);
    }

    /// <summary>
    /// Converts bar code scaling factor from percent to float.
    /// </summary>
    /// <param name="scalingFactor"></param>
    /// <returns></returns>
    private static float ConvertScalingFactor(string scalingFactor)
    {
        bool isParsed = false;
        int percent = TryParseInt(scalingFactor);

        if (percent != int.MinValue && percent >= 10 && percent <= 10000)
            isParsed = true;

        if (!isParsed)
            throw new Exception("Error! Incorrect scaling factor - " + scalingFactor + ".");

        return percent / 100.0f;
    }

    /// <summary>
    /// Implementation of the GetBarCodeImage() method for IBarCodeGenerator interface.
    /// </summary>
    /// <param name="parameters"></param>
    /// <returns></returns>
    public Image GetBarcodeImage(Aspose.Words.Fields.BarcodeParameters parameters)
    {
        if (parameters.BarcodeType == null || parameters.BarcodeValue == null)
            return null;

        BarcodeGenerator generator = new BarcodeGenerator(EncodeTypes.QR);

        string type = parameters.BarcodeType.ToUpper();

        switch (type)
        {
            case "QR":
                generator = new BarcodeGenerator(EncodeTypes.QR);
                break;
            case "CODE128":
                generator = new BarcodeGenerator(EncodeTypes.Code128);
                break;
            case "CODE39":
                generator = new BarcodeGenerator(EncodeTypes.Code39Standard);
                break;
            case "EAN8":
                generator = new BarcodeGenerator(EncodeTypes.EAN8);
                break;
            case "EAN13":
                generator = new BarcodeGenerator(EncodeTypes.EAN13);
                break;
            case "UPCA":
                generator = new BarcodeGenerator(EncodeTypes.UPCA);
                break;
            case "UPCE":
                generator = new BarcodeGenerator(EncodeTypes.UPCE);
                break;
            case "ITF14":
                generator = new BarcodeGenerator(EncodeTypes.ITF14);
                break;
            case "CASE":
                generator = new BarcodeGenerator(EncodeTypes.None);
                break;
        }

        if (generator.BarcodeType.Equals(EncodeTypes.None))
            return null;

        generator.CodeText = parameters.BarcodeValue;

        if (generator.BarcodeType.Equals(EncodeTypes.QR))
            generator.Parameters.Barcode.CodeTextParameters.TwoDDisplayText = parameters.BarcodeValue;

        if (parameters.ForegroundColor != null)
            generator.Parameters.Barcode.BarColor = ConvertColor(parameters.ForegroundColor);

        if (parameters.BackgroundColor != null)
            generator.Parameters.BackColor = ConvertColor(parameters.BackgroundColor);

        if (parameters.SymbolHeight != null)
        {
            generator.Parameters.ImageHeight.Pixels = ConvertSymbolHeight(parameters.SymbolHeight);
            generator.Parameters.AutoSizeMode = AutoSizeMode.None;
        }

        generator.Parameters.Barcode.CodeTextParameters.Location = CodeLocation.None;

        if (parameters.DisplayText)
            generator.Parameters.Barcode.CodeTextParameters.Location = CodeLocation.Below;

        generator.Parameters.CaptionAbove.Text = "";

        // Empiric scaling factor for converting Word barcode to Aspose.BarCode.
        const float scale = 1.0f; //2.4f;
        float xdim = 1.0f;

        if (generator.BarcodeType.Equals(EncodeTypes.QR))
        {
            generator.Parameters.AutoSizeMode = AutoSizeMode.Nearest;
            generator.Parameters.ImageWidth.Inches *= scale;
            generator.Parameters.ImageHeight.Inches = generator.Parameters.ImageWidth.Inches;
            xdim = generator.Parameters.ImageHeight.Inches / 25;
            generator.Parameters.Barcode.XDimension.Inches =
                generator.Parameters.Barcode.BarHeight.Inches = xdim;
        }

        if (parameters.ScalingFactor != null)
        {
            float scalingFactor = ConvertScalingFactor(parameters.ScalingFactor);
            generator.Parameters.ImageHeight.Inches *= scalingFactor;

            if (generator.BarcodeType.Equals(EncodeTypes.QR))
            {
                generator.Parameters.ImageWidth.Inches = generator.Parameters.ImageHeight.Inches;
                generator.Parameters.Barcode.XDimension.Inches =
                    generator.Parameters.Barcode.BarHeight.Inches = xdim * scalingFactor;
            }

            generator.Parameters.AutoSizeMode = AutoSizeMode.None;
        }

        //#if NET48 || JAVA
        return generator.GenerateBarCodeImage();
        //#elif NET6_0 || __MOBILE__
        //generator.GenerateBarCodeImage().Save(ArtifactsDir + "GetBarcodeImage.png");
        //return Image.Decode(ArtifactsDir + "GetBarcodeImage.png");
        //#endif
    }

    /// <summary>
    /// Implementation of the GetOldBarcodeImage() method for IBarCodeGenerator interface.
    /// </summary>
    /// <param name="parameters"></param>
    /// <returns></returns>
    public Image GetOldBarcodeImage(Aspose.Words.Fields.BarcodeParameters parameters)
    {
        if (parameters.PostalAddress == null)
            return null;

        BarcodeGenerator generator = new BarcodeGenerator(EncodeTypes.Postnet)
        {
            CodeText = parameters.PostalAddress
        };

        //#if NET48 || JAVA
        return generator.GenerateBarCodeImage();
        //#elif NET6_0 || __MOBILE__
        //generator.GenerateBarCodeImage().Save(ArtifactsDir + "OldBarcodeImage.png");            
        //return Image.Decode(ArtifactsDir + "OldBarcodeImage.png");
        //#endif
    }

    /// <summary>
    /// Parses an integer using the invariant culture. Returns Int.MinValue if cannot parse.
    /// 
    /// Allows leading sign.
    /// Allows leading and trailing spaces.
    /// </summary>
    public static int TryParseInt(string s)
    {
        return double.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out double temp)
            ? CastDoubleToInt(temp)
            : int.MinValue;
    }

    /// <summary>
    /// Casts a double to int32 in a way that uint32 are "correctly" casted too (they become negative numbers).
    /// </summary>
    public static int CastDoubleToInt(double value)
    {
        long temp = (long)value;
        return (int)temp;
    }

    /// <summary>
    /// Try parses a hex String into an integer value.
    /// on error return int.MinValue
    /// </summary>
    public static int TryParseHex(string s)
    {
        return int.TryParse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int result)
            ? result
            : int.MinValue;
    }
}

Here the Aspose_Words_BarcodeGenerator is basically a copy of your Github gist Aspose.Words for .NET. Generate сustom BarCode using C#. · GitHub with minor adjustments:

  1. Our implementation does not inherit from DocsExamplesBase, as we don’t have access to that class.
  2. I’ve set the const float scale to 1.0f; instead of your value 2.4f;, because the generated QR-codes in the PDF were much larger than the ones displayed in MS Word.
  3. Because we do not define the symbol NET48, I’ve commented out the respective directives (#if NET48 || JAVA, …).

At this point, when saving the results (of the code) as

at least I’m consistently having the QC-code image in the output.

In the original MS Word document Word_with_DisplayBarcode.docx, I’ve used blue lines (Shapes -> Lines of MS Word) around the QR-code to visualize its outline and thus the remaining alignment issues. Comparing the resulting Docx and PDF files, one can immediately see, that the outline of the QR-code in the Docx file still matches the blue lines, whereas the QC-code in the PDF document does not.

  1. The top left corner of the QR-code in the PDF document does not align with the blue markings.
  2. The QR-code in the PDF document is larger than the one in the docx file. (Although I’ve already set the const float scale to 1.0f; instead of your value 2.4f;, as described earlier!)

Both of these aspects negatively impact the PDF document, because in complex documents the QR-code is going to be misaligned and either overlaps or displaces other content and thus may disrupt the visual output (text alignment around the QR-code image, line breaks, textflow, …) of the document when comparing the docx and the PDF documents.

Is there any way to more precisely align the generated QR-code with the way MS Word renders the QR-code in their docx documents? And I’m not talking about the precise version of the QR-code (i.e. version 2 [25x25] vs. version 3 [29x29]), the mask pattern used to encode the data in the QR-code or anything like those details. It’s just about the size and positioning of the QR-code image in the resulting PDF.

These tests have been conducted using the latest versions of Aspose products (for .NET) at the time:

  • Words: 24.1.0
  • PDF: 23.12.0
  • BarCode: 23.12.0

Kind regards,
Matthias

@M.Heinz
We have opened the following new ticket(s) in our internal issue tracking system and will deliver their fixes according to the terms mentioned in Free Support Policies.

Issue ID(s): WORDSNET-26466

You can obtain Paid Support Services if you need support on a priority basis, along with the direct access to our Paid Support management team.