Skip to content

Changelog

What changed in each release of Tagua. All the packages (Tagua, Tagua.Excel, Tagua.Import, Tagua.Shaping.HarfBuzz and Tagua.Cli) are released together, with the same version.

Terminal window
dotnet add package Tagua
dotnet tool update --global Tagua.Cli

Tagua 0.22.0 converts a whole folder of RDLC reports at once and sums them up, for planning a migration. A table cell’s text can now show only where a condition holds, and the RDLC importer uses it: text that ReportViewer hides no longer prints in Tagua. A new guide walks through two migrations from start to finish.

tagua import takes a folder: it converts every .rdlc and .rdl report in it and its subfolders, and sums them up.

$ tagua import reports --out converted
Invoice.rdlc: 1 item to review (1 note(s)).
InvoiceList.rdlc: nothing to review.
...
5 report(s): 5 converted.
Nothing to review: 2 (40%)
One item or none: 4 (80%)
Five items or fewer: 5 (100%)
  • The summary lists the items most of the reports share, with how many have each, and is written with each report’s list to tagua-import-summary.md.
  • Notes and items: the import tells an item to review (a placeholder, or something converted differently) from a note (nothing to do, or only something from your application: the rows, a parameter’s value, a font’s files). ImportIssue.Note marks the notes.
  • Images of the same name: reports in one folder that each embed their own Logo keep their own.
  • Hidden text in tables: a text box hidden in a table cell, always or by a condition, is hidden in the template too. It used to print.

A column or a cell takes visible: its text shows only where the condition holds, and the cell keeps its width and borders.

columns:
- { header: Discount, value: "{Discount:P0}", visible: "=Discount > 0" }

A matrix’s total row, styled with borders or padding, drew a frame around its cells’ own borders.

  • RDLC migration examples: a sales invoice and a sales matrix, converted and rendered, with the reports to download.
  • The migration guide shows how to convert a folder of reports, and has the results for this version.

Tagua 0.21.0 lets a column header hold elements and gives a matrix’s values and group labels conditional styles. The RDLC importer uses both, and reads Business Central’s flags as booleans. 71% of the .NET reports in the public RDLC corpus now convert with nothing to review, up from 70%, and 67% of the Business Central reports, up from 63%.

A column takes headerContent: elements in place of its header text, laid out at the column’s width with the values the header sees, as a cell’s content is.

columns:
- value: "{Amount:C}"
headerContent:
- type: stack
direction: row
content:
- { type: text, value: Amount, width: 60pt }
- { type: text, value: "(EUR)" }

Group and sort keys can be booleans, false before true.

A matrix’s values and groups take when, as table cells do:

  • A value’s rules are decided per cell: =Sum(Amount) < 0 reads the cell’s rows, in totals too.
  • A group’s rules are decided per label, over that group’s rows.
  • Fields compared with True or False are read as booleans when they have no declared type or are declared as text, as Business Central’s flags are. Their “True”/“False” text reads as a boolean.
  • A rectangle, image, subreport or layout grid in a column’s header becomes its headerContent, laid out as its items sit: text boxes side by side stay side by side. An empty rectangle, or one with a single text box, is just the text, without a note.
  • Conditional styles on a matrix’s values and labels are kept.

The migration guide has the results for this version.

Tagua 0.20.0 lets a parameter’s default read a dataset and adds base64 functions for images kept as text. The RDLC importer uses both, and converts the user running the report (User!UserID) as a parameter. 70% of the .NET reports in the public RDLC corpus now convert with nothing to review, up from 68%, and 63% of the Business Central reports, up from 49%, mostly because the user’s name they print now comes from a parameter.

A default can read a dataset through aggregates that name it, over all of its rows:

parameters:
customer: { type: string, default: "=First(Id, 'customers')" }
  • When: it is worked out once the data is read. Other parameters are still checked before any data is fetched.
  • Errors: if the data cannot give the value, the error is reported and the parameter is left empty; the report still renders. An aggregate that does not name a dataset is a template error.
  • FromBase64(text) gives the bytes a base64 text holds, so an image kept as text can be a source: "{FromBase64(Logo)}". A data: URI’s prefix is skipped.
  • ToBase64(bytes) gives bytes as base64 text.
  • User!UserID reads a string parameter UserID, empty unless your application passes the user’s name. The import notes it once.
  • Images kept as base64 text convert: Convert.FromBase64String(...) becomes FromBase64(...). In a database image, Convert.ToBase64String(Fields!Picture.Value), as Business Central reports show company pictures, is the picture’s bytes. A field used that way with no declared type is read as bytes.
  • Parameter defaults from a dataset take its first value, converted to the parameter’s type. A list of valid values from a dataset only serves ReportViewer’s prompt; the import notes that your application passes the value.
  • More defaults convert: String.Empty, DateTime.Now and Today, literal dates (2024-01-01), and an empty value on a nullable parameter.
  • DataSets!Orders.CommandText becomes the query text written in the report.
  • Drill-down toggles: the note now says what the PDF does. The item shows as it starts, as ReportViewer’s PDF export shows it.
  • The migration guide has the results for this version. “Nothing to review” now also allows notes about values your application supplies, such as the user’s name.
  • The parameters reference and the expressions reference describe defaults that read a dataset and the base64 functions.

Tagua 0.19.0 converts more of what RDLC reports hold: rows and columns beside matrix groups, formats taken from the data, references to other text boxes, RDL 2005 matrices and layout grids in table cells. 68% of the .NET reports in the public RDLC corpus now convert with nothing to review, up from 65%, and 92% of the Business Central reports have five items or fewer, up from 68%.

A row or column group takes beside: rows or columns next to its instances, each with its own header and values, such as an average beside the months.

columns:
- group: "{Month}"
total: Total
beside:
- { header: Average, values: ["=Avg(Amount)"] }
  • before: true puts it ahead of the instances instead of after them.
  • values takes one expression for all of the matrix’s values, or one for each.
  • Where a column beside meets a row beside, the column’s value is used.

A text that is one {Format(Amount, AmountFormat)}, with a format taken from the data, keeps its value and its format as {Amount:C} does, so the Excel export writes a number.

  • Rows and columns beside matrix groups convert as beside, instead of keeping one as the total and listing the others.
  • Formats that are expressions convert to Format(value, format). Business Central reports keep their formats in fields: 4,483 of 4,551 notes about them are gone.
  • References to other text boxes (ReportItems!Name.Value) take that text box’s expression: in the same table row, and in the page header and footer, which read the page’s first and last rows. A document’s values carried to the page header in one hidden text box, Choose(3, Split(CStr(ReportItems!Values.Value), Chr(177))), become the value itself. Chr and CBool convert too.
  • RDL 2005 matrices convert as matrices, with their groups, subtotals and static columns. They were placeholders.
  • Layout grids in table cells, tablixes with no detail rows or groups, convert as rows of their cells instead of placeholders. Tables in cells that list rows are still listed.

The importer could crash when a fix it made to a template removed an element that a later fix in the same pass pointed into.

The migration guide has the results for this version.

Tagua 0.18.0 lets a report call your application’s own functions, and the RDLC importer uses that to convert custom code: the Visual Basic in a report’s Code becomes a class your application builds and registers.

Pass .NET functions to CompiledReport.Compile in ReportFunctions, and the report’s expressions call them by name. Calls are checked against their types like the built-in functions’:

var functions = new ReportFunctions()
.Add("RowColor", (int row) => row % 2 == 0 ? "#F2F2F2" : "#FFFFFF")
.Add("Words", (decimal amount) => AmountInWords(amount));
var report = CompiledReport.Compile(template, functions);
  • Types: parameters and results can be text, numbers, booleans, dates and times, or their nullable forms; a parameter can also take any value. Up to four parameters.
  • Empty values: an empty argument to a parameter that cannot take it leaves the value empty without a call.
  • Errors: an exception is reported against its row, like any failed expression, and the report still renders.
  • Subreports can call them too. It works with native AOT: the functions are compiled into your application.

Custom code converts. When a report calls its code, the importer writes it out next to the template as a Visual Basic class (Invoice.Code.vb), and Code.GetColor(...) becomes a call to GetColor(...). Build it in a Visual Basic class library and register its functions:

var functions = new InvoiceCode().Register(new ReportFunctions());
var report = CompiledReport.Compile(template, functions);
  • As in ReportViewer: the Visual Basic compiler builds it, so it keeps its state (a row counter, say), and arguments are converted as Visual Basic converts them.
  • Listed for review: what cannot be registered (a Sub, ByRef or optional parameters), and code that reads Report.Parameters, which Tagua does not pass.
  • Before it is registered, ImportResult.Functions stands in for the functions, to check or preview the template.
  • Code a report never calls, such as Business Central’s usual code, produces no file.

In the corpus, a report that calls custom code now has one thing to do (build and register the class) instead of an item for each expression: 68% of the Business Central reports have five items or fewer to review, up from 65%. The share of .NET reports with nothing to review is unchanged, at 65%.

Tagua 0.17.0 lets a matrix total come before its group, and the RDLC importer now converts RDL 2005 lists, which were placeholders. 65% of the .NET reports in the public RDLC corpus convert with nothing to review.

A row or column group takes totalBefore: its total comes before the group’s instances, above them or to their left.

rows: [ { group: "{Region}", total: All regions, totalBefore: true } ]
  • RDL 2005 lists convert. A List’s report items are laid out as they sit, once per group when it has a grouping (as RDL 2005 shows it) or once per row. It then converts as a tablix list does. In the corpus, 55 list placeholders in 46 reports are gone, and their contents show.
  • A matrix total before its group stays there. 91 notes that it was shown after are gone.
  • With several rows or columns beside a matrix group, the total is the first after the group, usually the real one; the one before is typically a heading. The note says the others were left out.
  • An empty Hidden no longer reports an unconverted visibility.

The migration guide has the results for this version.

Tagua 0.16.0 gives matrices values in rows and makes their row groups optional. A table’s header now sees its first row, and a table’s totals cover its own rows. The RDLC importer uses all of it, and 65% of the .NET reports in the public RDLC corpus now convert with nothing to review, up from 64%.

  • A table’s header is evaluated like the rows before its data: with the table’s first row, and aggregates that cover the table’s rows. A header’s {Count()} used to count the report’s rows.
  • Inside a table, a dataset’s name as a scope (Sum(Amount, 'orders')) covers all of that dataset’s rows, as in RDLC. It used to mean the table’s rows. Without a scope, aggregates in a table cover the table’s rows, as before.
  • rows is optional. Without row groups, a matrix is one row of values, like a one-row cross-tab.
  • valuesInRows lists the values down: a row for each under every row group, headed by the value’s header, and one column under each column group.
- type: matrix
dataset: sales
columns: [ { group: "{Quarter}" } ]
values:
- { header: Sales, value: =Amount, format: C0 }
- { header: Orders, value: =Amount, aggregate: count }
valuesInRows: true
  • A header sees the first row, so it can show a field of the table’s data: "{Count()} orders from {Region}".
  • With no rows, the header, the rows before the data and the footer read a blank row: their fields show empty instead of failing.
  • More tablixes with column groups convert as matrices: column groups over static rows only, and several rows under the innermost row group, which become values in rows.
  • Table headers that read the table’s fields convert. They were the most common unknown names: those notes drop from 87 reports to 22.
  • Names in another case are written as declared. RDL names are not case-sensitive, and Tagua’s are.
  • IIf(Sum(X) = 0, "", Sum(X)) (blank for zero) translates with nothing for the blank, so both branches have one type.

A filtered table over the body’s own dataset totalled all of the dataset’s rows in its footer, not the table’s.

The migration guide has the results for this version.

Tagua 0.15.0 gives bands margins, so a band can be a box narrower than the page. The RDLC importer uses them to keep a grouped table’s frame, and it now keeps the layout of rectangles in table cells. 64% of the .NET reports in the public RDLC corpus now convert with nothing to review, up from 62%.

A band takes margin: space outside its background and borders. With side margins, it is a box narrower than the page, moved in from the left:

detail:
- margin: 0 120pt 0 40pt
background: "#F7F7F7"
borderLeft: "0.5pt solid #999999"
borderRight: "0.5pt solid #999999"
layout: row
content: [ ... ]

The Excel export places a band’s content within its side margins.

  • A grouped table keeps its frame. When a table’s rows become the report’s groups, every band from its rows gets the table’s background and side borders, at the table’s place and width. The first band gets the top border and the last the bottom one. In the corpus, 75 .NET and 9 Business Central reports had a frame that was left out.

  • Rectangles in table cells keep their layout. A rectangle in a cell became its text boxes as lines of text, with its other items left out. It is now the cell’s content, laid out as its items sit:

    • side by side in rows at their widths, with the space between them;
    • with its lines, images, subreports and nested rectangles;
    • with its box on the cell.

    In a row band, a rectangle becomes a stack, and a table, matrix or chart in it converts. A layout grid in a rectangle becomes rows of cells. Rectangle notes drop from 125 reports to 58.

The migration guide has the results for this version.

Tagua 0.14.0 is about charts and layout grids. A chart’s category is optional, and a series value can hold its own aggregates. The RDLC importer keeps most of the charts it used to drop, and the layout tables it used to leave out. 62% of the .NET reports in the public RDLC corpus now convert with nothing to review, up from 60%.

  • The category is optional. Without one, all the rows are one category: the series stand side by side, and a pie or doughnut shows a slice per series.
  • A series value can hold aggregates, computed once per category over its rows.
  • aggregate: count takes any value, text too.
series:
- { name: Price, value: "=Sum(Revenue) / Sum(Units)", format: C2 }
- { name: Orders, value: =OrderId, aggregate: count }
  • Layout grids are kept. A tablix with no detail rows or groups (a form header, a signature block) was left out of the converted report. Alone on its line, its rows now become bands that grow with their text, and a hidden row folds away. Beside other items, its cells are placed where they are. In the corpus, 152 .NET and 80 Business Central reports had one.
  • Charts keep their series. Fields plotted in a chart are read as numbers, a display Format around a value is taken off, and text still left is read with ToDouble. Chart notes drop from 182 reports to 52.
  • Charts without a category group: a series group serves as the category. With static series only, the chart has no category.
  • RDL 2005 charts convert.
  • Nested regions: a repeating region with no dataset, inside another, reads the enclosing one’s dataset.

The designer’s empty placeholder series no longer produces a note.

The migration guide has the results for this version.

Tagua 0.13.0 reads text as HTML, takes styles from the data, and lets a matrix value hold its own aggregates. The RDLC importer uses all three, with more tablixes converting as matrices and report variables carried over, and 60% of the .NET reports in the public RDLC corpus now convert with nothing to review, up from 58%.

Texts, table columns and cells take markup: html. The text is read as HTML once its expressions are filled in, so markup from data or parameters works too:

- type: text
value: "<b>GSTIN:</b> {TaxId}<br>{HtmlEncode(Address)}"
markup: html
  • Tags: b, strong, i, em, u, s, br, paragraphs and divs, lists and headings, font color, and the colour, weight, style and decoration of a style attribute. Entities are read and spaces collapse, as in a browser. Other tags are left out with their text kept; font sizes and faces are not used.
  • PDF: bold and italic change faces within a line, so wrapping, justification and right-to-left text work as they do for plain text. Coloured, underlined and struck-out parts are drawn as such.
  • Excel: the cell shows the text without its tags, with its line breaks.
  • HtmlEncode(text) shows a value as it is inside HTML: its &, < and line breaks too.

Texts, table columns and cells take styleFrom, and tables headerStyleFrom: a colour, background, font, size or weight computed for each value, after its when rules:

- type: text
value: "{Status}"
styleFrom: { color: =StatusColor, fontWeight: "=Priority > 2" }
  • Values: colours as #RRGGBB, colour names or rgb(); sizes in points or with a unit; weights as bold, normal, a number or true and false.
  • Checks: a value that is no colour or size leaves the style as it is and is reported. An expression of the wrong type is an error before filling.
  • New functions: RunningCountDistinct, for stripes that change with each new item, and IndexOf(text, value).

A matrix value that holds aggregates is computed once per cell, over the cell’s rows, totals included. It can be text:

values:
- { header: Price, value: "=Sum(Amount) / Sum(Quantity)", format: C2 }
- { header: Grade, value: "=First(Grade)" }
  • HTML placeholders become markup: html. Plain runs beside them are encoded, so they show as they are. In the corpus, HTML shown as text drops from 112 reports to 5.
  • Styles computed from a field, parameter or variable become styleFrom. Style expressions left out drop from 198 reports to 37; most of the rest is custom code.
  • Report variables become the template’s variables, and Variables!X.Value reads them.
  • More tablixes with column groups convert as matrices: header rows and columns around their groups are looked through, and any value expression converts. Fallbacks to a table drop from 93 reports to 35.
  • Translated now: a number as an IIf condition (RowNumber(Nothing) Mod 2), String.IsNullOrEmpty, InStr, and RunningValue with CountDistinct.
  • A table row with its own cells, such as a matrix’s total row, drew its background and borders across the whole width, past the table’s last column.
  • The importer split =Sum(A) / Sum(B), in a chart or matrix value, as if it were one Sum.

The fonts and styles reference documents styles from the data, and the migration guide has the results for this version.

Tagua 0.12.0 lets table cells hold elements: an image, a subreport filled for the row, a stack of texts. It also adds CountDistinct. The RDLC importer uses both, and 58% of the .NET reports in the public RDLC corpus now convert with nothing to review, up from 54%.

A cell of any table row (before, detail, footer, or a group’s header and footer) takes content, a list of elements, in place of value:

detail:
- cells:
- value: "{Product}"
- content:
- { type: image, source: "{Photo}", height: 40pt }
- content:
- type: subreport
template: OrderLines.report.yaml
parameters: { Order: =Id }
  • Filling: the elements are filled with the row’s values, as a band’s are. A subreport’s parameters and an image’s source see the row.
  • PDF: they’re laid out top to bottom at the cell’s width, the row grows to fit, and they’re tagged inside the cell.
  • Excel: a subreport’s rows follow the table row, in the cell’s columns.
  • Not yet: a table, matrix or chart in a cell.

A new aggregate: CountDistinct(Customer) counts the different values, skipping nulls, and takes a scope like the others.

  • Subreports and images in table cells become the cell’s content. In the corpus, subreport placeholders in cells drop from 47 repositories to 2, and images from 34 to 6.
  • The grouped rows’ cells: an image, subreport, table or matrix in one becomes that element, in the row’s band.
  • Groups on several expressions group on them all. They used to group on the first only.
  • CountDistinct converts, except inside RunningValue.
  • Matrices keep their cells’ styles: label and value fonts, colours, borders and padding, and header and total styles.

A mapping in a converted template, short enough for one line but holding a list too long for one, was written with an empty value, and the template didn’t load.

The migration guide now says how the converter is tested on about 6,000 public RDLC reports, with the results for this version.

Tagua 0.11.0 brings groups inside tables and detail rows: a table can group its own rows, with a heading and a subtotal per group, and show a record over more than one row. The RDLC importer uses both, and 54% of the .NET reports in the public RDLC corpus now convert with nothing to review, up from 52%.

A table takes groups: runs of consecutive rows with the same key, each with its own header and footer rows.

- type: table
dataset: orders
sort: [Region, Date]
columns: [ ... ]
groups:
- name: region
by: =Region
header:
- cells:
- { value: "{region} · {Count()} orders", span: 3 }
footer:
- cells:
- { value: "Total {region}", span: 2 }
- { value: "{Sum(Amount):C}", align: right }
  • Totals: in a group’s rows, Sum(Amount) covers the group. In data rows and the table’s own rows it covers the table, as before. Sum(Amount, 'region') names the group explicitly.
  • The group’s name reads its current key inside the table ({region}).
  • Layout: groups nest, and a group’s heading never ends a page alone.
  • Excel: each group folds into its subtotal, as the body’s groups do.

A table takes detail: the rows each data row shows, in place of the columns’ values. Use them for a record that takes more than one row, such as a line with its description under it, or for cells that span columns.

detail:
- cells: [ { value: "{Item}" }, { value: "{Amount}", align: right } ]
- visible: =Note != null
cells: [ { value: "{Note}", span: 2, italic: true } ]

A column’s value is now optional: it’s required when a table has no detail rows. For .NET users of the definition model, TableColumn.Value is now string?.

  • Grouped tablixes: every grouped tablix but the first, which still becomes the report’s groups, becomes a table with groups. It used to list its rows without the group rows. In the corpus, that drops from 83 repositories to none.
  • Several detail rows: a tablix with several detail rows converts them all, where it used to keep only the first (96 repositories to none).
  • Missing datasets: a data region or chart without a DataSetName reads the report’s only dataset, as RDLC does.

Tagua 0.10.0 adds conditional styles: a negative amount in red, zebra stripes, a total in bold past a threshold. The RDLC importer turns RDLC’s conditional styles into them, and reads fields as the type the report uses them as.

Texts, table columns (for their data cells) and before and footer cells take when: a list of rules, each a condition with style properties and, optionally, a named style.

- header: Amount
value: "{Amount:C}"
when:
- { if: =Amount < 0, color: "#B3261E" }
- { if: "=RowNumber() % 2 == 0", background: "#F2F2F2" }
  • Row by row: where a rule’s condition holds for the value, its named style and then its properties go over the value’s style. Later rules go over earlier ones.
  • Checked before filling: a condition must be a boolean, and a named style must exist. As with visible, conditions can’t use page numbers outside page headers and footers.
  • PDF and Excel: both apply them. In a sheet, a cell’s colour and fill change row by row.
  • The statement example now shows debits in red.
  • Conditional styles: a style that picks among constants, such as =IIf(Fields!Amount.Value < 0, "Red", "Black"), a Switch, or IIf(RowNumber(Nothing) Mod 2, ...) stripes, becomes when rules. Style expressions left out drop from 98 repositories to 34 in the public RDLC corpus.
  • Field types from their use: a field with no declared type takes the type the report uses it as: summed or compared with a number, used with a date function or format, or used as a condition. So does a field declared as text that the report only sums, as Visual Basic converts it. Each such field is listed among the issues. In the corpus, errors from numbers read as text drop from 425 files to 158.
  • Overall: 52% of the .NET reports in the corpus now convert with nothing to review beyond font and layout notes, up from 47% in 0.9.0.

A format a value can’t take, such as {Amount:Z9} or a date format with an unmatched quote, failed the whole fill. It now shows #Error, with a diagnostic, as SSRS does.

Tagua 0.9.0 was checked against about 6,000 public RDLC reports. It fixes the crashes they found, converts more of them as they are, and brings table frames and header styles to the Excel export.

About 6,000 .rdlc files from 1,500 public GitHub repositories were imported, filled with sample rows, laid out and rendered: 4,764 from .NET applications and 1,230 from Business Central extensions. Before this release one in ten of the .NET files crashed on the way. Now all but two get through, and 47% convert with nothing to review beyond font and layout notes, up from 41%.

  • Text of only spaces crashed line breaking, in any template, since 0.3.0.
  • A date with a format that includes a time, such as {Today():G}, threw an error. It now shows the date at midnight, as a date and time would. Date formats such as d and o are unchanged.

ToDateTime(value) turns a date into a date and time at midnight, and reads text in the report’s culture: ToDateTime('2026-09-26 14:30').

  • More reports convert as they are:
    • Nullable field types (System.Nullable`1[System.Decimal]) are read as their value’s type, not as text.
    • CDate, Globals.PageNumber (with a dot), and x Is Nothing / x IsNot Nothing are converted.
    • A chart of type Shape without a subtype is a pie, RDL’s default. It used to be a placeholder.
  • A tablix’s own border and background frame the converted table or matrix.
  • What used to make an import fail now converts, with an issue:
    • A group named after its own field gets a name of its own.
    • A group on row numbers (so many rows per page) is left out.
    • A chart series or matrix value that can’t be converted is left out, and a chart left with no series goes too.
    • Table rows are fitted to the table’s columns.
    • A pie’s series group is dropped, as a pie shows one series.
    • Text ending in a line break, and commas in lists, are written so the template reads back.
  • A table’s own border and background now frame its cells, as in the PDF.
  • Table header styles keep their background and borders.
  • A cell spanning several columns keeps its borders across all of them.

Tagua 0.8.0 draws a table’s own border, background and padding, framing all its rows.

A table’s border, background and padding used to be ignored. They now frame the whole table, and the table still breaks between rows:

- type: table
dataset: items
border: "1pt solid #1F1F1F"
background: "#F4F4F4"
padding: 4pt 6pt
columns: [ ... ]
  • Every row gets the background and the side borders. The first row gets the top border and the last the bottom one, and padding is space inside the frame.
  • On a following page, the frame reopens above the repeated header, with the table’s top padding and border, as it opened on the first page.
  • A row taller than a page divides its frame at the cut, like any other box.
  • Not in Excel yet: the Excel export doesn’t draw the table’s outer border.

The site now has a changelog listing every release.

Tagua 0.7.0 improves fonts in converted reports: Times joins the standard fonts, Windows font names map onto them, and tagua import lists every font a report needs, with free substitutes that have the same metrics.

  • Times: Times is now a standard PDF font (regular, bold, italic, bold italic), with Adobe’s own metrics for every character of the WinAnsi encoding.
  • Windows names: Times New Roman and serif map onto Times, and Courier New onto Courier, as Arial already mapped onto Helvetica. These are the substitutions PDF viewers make, and the widths match, so text in converted reports wraps as it did.
  • Validator: it now recognises the same names. It refuses them under PDF/A and PDF/UA, which need every font embedded, and says plainly when any other undeclared font will fall back to Helvetica.

RDLC and SSRS reports name fonts installed on Windows. tagua import now lists each one Tagua doesn’t have, with a free font that has the same metrics where one exists:

In the report Free, with the same metrics
Calibri Carlito
Cambria Caladea
Segoe UI Selawik

Declare the substitute under the report’s own font name, and every style that uses it keeps working:

fonts:
Calibri: { regular: fonts/Carlito-Regular.ttf, bold: fonts/Carlito-Bold.ttf }

For PDF/A and PDF/UA, Liberation Sans, Serif and Mono have the metrics of Arial, Times New Roman and Courier New. See the Fonts section of the migration guide.

Tagua 0.6.0 makes long content flow across pages: table rows, boxes and stacks taller than a page now split instead of being cut off. Tables can also have rows before their data, such as an opening balance.

Content taller than a page splits across pages

Section titled “Content taller than a page splits across pages”
  • Rows that fit a page stay whole: a table row or box that doesn’t fit the rest of the page moves whole to the next page, as before.
  • Taller than a whole page: the content now splits, starting on the page it reaches. This covers a table row with long text, a box with a background and borders, a stack, a fixed-height box, or an absolute band.
    • It cuts only between lines of text, never through a line, image, chart or barcode.
    • A box’s background and side borders continue onto the next page, and the box is left open at the cut, like CSS’s default box-decoration-break: slice.
    • A table’s header repeats above the continuation.
  • Accessibility: a split cell or box stays one element for screen readers, with its content on several pages, and the PDF stays valid PDF/UA and PDF/A.
  • Still cut off: only content with no possible cut point, such as an image taller than the page, is cut off, with a warning.

A table’s new before rows sit between the column headers and the data rows. They work like footer rows, with cells that span columns, row headers, styles and visible. They see the table’s first data row, as footer rows see its last, plus the table’s totals.

before:
- cells:
- { value: Opening balance, span: 3, isHeader: true }
- { value: "{Opening:C}", align: right }
  • Page breaks: when the table continues onto another page, the column header repeats but the before rows don’t.
  • Empty tables: the before rows still show when the table has no rows, and appear in the Excel export in order.

See the account statement example: opening balance, transactions with a running balance, closing balance.

tagua import now converts a table’s extra header rows (such as a “Brought forward” line above the details) into before rows. It used to drop them with a note.

Tagua 0.5.0 adds running totals: running balances, cumulative figures and row numbers, in bands and in tables. tagua import now converts SSRS RunningValue and RowNumber too.

New aggregate functions: RunningSum, RunningCount, RunningAvg, RunningMin and RunningMax. They aggregate a scope’s rows from the first up to the current one. RowNumber() numbers the rows from 1.

columns:
- { header: Amount, value: "{Amount:C}", align: right }
- { header: Balance, value: "{Opening + RunningSum(Amount):C}", align: right }
  • Scope: they take the same optional scope as Sum and default to the same one. In a group’s detail they start again with each group, and RunningSum(Amount, 'report') runs on across the whole report.
  • Headers, footers and tables: a group header shows the value at the group’s first row, and its footer at its last row, which is the group’s total. In a table they follow the table’s rows, and the table’s footer shows the final value.
  • Exact: sums of decimals stay exact, like the other aggregates.

See Running totals in the expression reference, and the running balance in the account statement example.

tagua import (and Tagua.Import) now converts RunningValue(…, Sum|Count|Avg|Min|Max, scope) and RowNumber(scope), which used to be left as placeholders. With Nothing as the scope, they take the template’s default: the table inside a table, the innermost group elsewhere. SSRS counts across the whole data region there, so check grouped reports. RunningValue with CountDistinct is still listed for review.

Tagua 0.4.0 adds matrices (cross-tabs, or pivot tables): a dataset summarised down the side and across the top, with subtotals. tagua import now converts SSRS matrices too.

A new element, matrix:

- type: matrix
dataset: sales
sort: [Month]
headerStyle: head
totalStyle: total
rows:
- { group: "{Region}", header: Region, total: All regions }
- { group: "{Product}", header: Product, total: Total }
columns:
- { group: "{Month:MMM}", total: Jan–Jun }
values:
- { value: =Revenue, format: C0 }
  • Groups: rows are grouped down the side by rows, nested, and across the top by columns. Groups appear in the order their first row does, so sort orders them.
  • Values: each of values is aggregated where a row group meets a column group: sum (the default), avg, count, min or max, each with a format. Sums of integers and decimals are exact. Several values sit side by side under each column group.
  • Totals: a group with total gets a labelled total of its instances. Totals work down and across: subtotals per group, total columns, and the grand total.
  • Styles: headerStyle styles the column headers and totalStyle the total rows and columns. A group’s label shows once per group, and a column header spans the columns under it.
  • Pages and accessibility: a matrix breaks across pages between rows and repeats its column headers. In the PDF it’s tagged as a table, with column headers and row headers, and it’s valid PDF/UA.
  • Excel: the export keeps the matrix’s numbers as numbers, with their formats, and merges each header over its columns.

See the matrix example and the matrix reference.

tagua import (and Tagua.Import) now converts a tablix with column groups into a matrix instead of listing it for review:

  • nested row and column groups, labelled from their header text boxes;
  • totals from the members beside a group, and row-group titles from the corner;
  • the values from the cells where the innermost groups meet, such as =Sum(Fields!Sales.Value), with their number formats, including several values under a column group;
  • filters and group sorting.

A matrix with detail rows or with groups side by side is still converted as a table, with an issue listed. Cell styles other than number formats are not converted yet.

  • Empty table cells keep their place in the PDF’s structure tree. An empty cell used to be left out, so its row had fewer columns than the others, which PDF/UA checkers reject. This affected any table with an empty cell.

taguareports.com now has a pricing page and a contact page, for licences and for help migrating RDLC, SSRS or Crystal Reports.

Tagua 0.3.0 adds hyphenation: justified and narrow columns can break long words at the syllables their language allows.

A new style property, hyphens. It follows CSS, and is inherited like other text properties, so it can go on the default style:

styles:
default: { font: Inter, hyphens: auto }
  • manual (the default): lines can break at soft hyphens (U+00AD) in your text, and a hyphen is drawn only where a line actually breaks.
  • auto: a word that doesn’t fit the rest of its line breaks at the last point its language allows that still leaves room for the hyphen. This is Liang’s algorithm, as TeX uses. The language is the element’s language, or the document’s. Abbreviations in capitals, words with digits, and words with soft hyphens aren’t hyphenated automatically.
  • none: words never break with a hyphen, and soft hyphens are ignored.

Languages: Danish, Dutch, English, Estonian, Finnish, French, German (1996 spelling), Italian, Norwegian, Polish, Portuguese, Spanish and Swedish. These are the TeX patterns from the hyph-utf8 project, embedded unmodified. For other languages, load TeX patterns with Hyphenator.FromTex and pass them in LayoutOptions.Hyphenators.

Accessibility: a hyphen added at a line break reads back as a soft hyphen. Copy-paste, search and screen readers see the whole word, and PDF/UA stays valid.

See it in the line breaking and hyphenation example, and the hyphens reference.

  • Soft hyphens are no longer drawn in the middle of a line. Fonts that have a glyph for U+00AD (Arial, DejaVu and others) used to show a soft hyphen as a visible hyphen with the built-in shaper. It’s now invisible unless a line breaks there. HarfBuzz already treated it that way.

The pattern files are listed in THIRD-PARTY-NOTICES.md with their copyright and licence: MIT, BSD-3, or similar permissive terms, and the LPPL for Swedish, distributed unmodified. They add about 320 KB to Tagua.dll.

Tagua 0.2.0 adds Excel output: the same template and data that make the PDF now make an .xlsx workbook.

The workbook is built from the report’s data, not its pages, so it’s ready to sort, filter and sum.

  • Typed cells: a text that is a single expression, such as {Amount:C} or {Issued:d}, becomes a real number or date. Its Excel format is translated from the template’s .NET format in the report’s culture. Values keep full precision.
  • Layout:
    • Columns follow the report’s layout, and values that span columns are merged.
    • Groups fold into their totals with outline levels.
    • The page header is frozen at the top.
    • Styles, borders and fills carry over, and printing fits the page width.
    • Subreports are laid out in place, and right-to-left rows and tables start at the right.
  • Images become pictures in their cells, sized as in the PDF, including fit: cover and none cropping.
  • Charts become native Excel charts of the same kind (column, bar, line, area, pie, doughnut, stacked or not), with the title, legend, labels, colours and number format. Their values go on a Chart data sheet, so editing a value updates the chart.
  • Barcodes become sharp pictures that scanners read. CI decodes every sample barcode from its workbook.
  • Sparklines become Excel’s own in-cell sparklines where they match the PDF, and small charts without axes otherwise.
  • No dependencies: the workbook is written directly. It runs under native AOT, and the same input gives the same bytes.

API:

var result = await ExcelReports.RenderExcelAsync(report, inputs, stream);

For a report that’s already filled, use new ExcelRenderer(options).Render(filled, stream). ExcelOptions sets the sheet name, and the resource resolver used for images.

CLI: tagua render invoice.report.yaml --out invoice.xlsx picks Excel from the file extension, including in --watch mode.

  • Number formats without a precision now give 2 decimals on every system. This covers N, F and P without a number ({Total:N}, {Share:P}). .NET used to take the precision from the operating system: 2 on Windows, 3 on macOS and Linux. Reports rendered on macOS or Linux with these formats now show 2 decimals, matching Windows and SSRS. Currency (C) keeps each currency’s own precision. To keep 3 decimals, write the precision, as in {Total:N3}. (#32)

taguareports.com now has:

  • the guides: PDF/A, PDF/UA, Factur-X, migrating from RDLC, and Excel export;
  • the expression reference;
  • an examples section, where every example shows its template, PDF and workbook.

Every package now links to the site as its project website, and each package’s README links to its own docs page.

The first release.

  • Templates in YAML or JSON, with a JSON Schema for editors: bands, groups, tables, images, barcodes (QR, Data Matrix, PDF417, Code 128, EAN-13), charts (column, bar, line, area, pie, doughnut) and subreports, with a sandboxed, type-checked expression language.
  • Standards: tagged PDF, PDF/UA-1 and PDF/UA-2, PDF/A-2b, PDF/A-3b and PDF/A-4(f), and Factur-X and ZUGFeRD hybrid invoices, checked before any data is read.
  • Any script: the Unicode Bidirectional and Line Breaking Algorithms, Chinese, Japanese and Thai line breaking, OpenType shaping with HarfBuzz (Tagua.Shaping.HarfBuzz), font fallback, and justification with kashidas.
  • Migration: tagua import (Tagua.Import) converts SSRS and ReportViewer RDLC and RDL reports into templates.
  • The tagua command (Tagua.Cli): render a template to PDF, with --watch to preview as you edit.
  • Native AOT compatible throughout.