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.
dotnet add package Taguadotnet tool update --global Tagua.Cli0.22.0 (2026-09-29)
Section titled “0.22.0 (2026-09-29)”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.
RDLC import
Section titled “RDLC import”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 convertedInvoice.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.Notemarks the notes. - Images of the same name: reports in one folder that each embed their own
Logokeep 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.
Tables
Section titled “Tables”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.
Website
Section titled “Website”- 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.
0.21.0 (2026-09-29)
Section titled “0.21.0 (2026-09-29)”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%.
Tables
Section titled “Tables”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.
Matrices
Section titled “Matrices”A matrix’s values and groups take when, as table cells do:
- A value’s rules are decided per cell:
=Sum(Amount) < 0reads the cell’s rows, in totals too. - A group’s rules are decided per label, over that group’s rows.
RDLC import
Section titled “RDLC import”- Fields compared with
TrueorFalseare 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.
Website
Section titled “Website”The migration guide has the results for this version.
0.20.0 (2026-09-28)
Section titled “0.20.0 (2026-09-28)”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.
Parameters
Section titled “Parameters”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.
Expressions
Section titled “Expressions”FromBase64(text)gives the bytes a base64 text holds, so an image kept as text can be asource:"{FromBase64(Logo)}". Adata:URI’s prefix is skipped.ToBase64(bytes)gives bytes as base64 text.
RDLC import
Section titled “RDLC import”User!UserIDreads a string parameterUserID, empty unless your application passes the user’s name. The import notes it once.- Images kept as base64 text convert:
Convert.FromBase64String(...)becomesFromBase64(...). 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.NowandToday, literal dates (2024-01-01), and an empty value on a nullable parameter. DataSets!Orders.CommandTextbecomes 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.
Website
Section titled “Website”- 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.
0.19.0 (2026-09-28)
Section titled “0.19.0 (2026-09-28)”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%.
Matrices
Section titled “Matrices”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: trueputs it ahead of the instances instead of after them.valuestakes 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.
RDLC import
Section titled “RDLC import”- 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.ChrandCBoolconvert 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.
Website
Section titled “Website”The migration guide has the results for this version.
0.18.0 (2026-09-28)
Section titled “0.18.0 (2026-09-28)”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.
Your application’s functions
Section titled “Your application’s functions”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.
RDLC import
Section titled “RDLC import”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,ByRefor optional parameters), and code that readsReport.Parameters, which Tagua does not pass. - Before it is registered,
ImportResult.Functionsstands 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%.
Website
Section titled “Website”- The migration guide explains custom code, and has the results for this version.
- The expressions reference shows your application’s functions.
0.17.0 (2026-09-28)
Section titled “0.17.0 (2026-09-28)”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.
Matrices
Section titled “Matrices”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 } ]RDLC import
Section titled “RDLC import”- 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
Hiddenno longer reports an unconverted visibility.
Website
Section titled “Website”The migration guide has the results for this version.
0.16.0 (2026-09-28)
Section titled “0.16.0 (2026-09-28)”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%.
Behaviour changes
Section titled “Behaviour changes”- 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.
Matrices
Section titled “Matrices”rowsis optional. Without row groups, a matrix is one row of values, like a one-row cross-tab.valuesInRowslists 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: trueTables
Section titled “Tables”- 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.
RDLC import
Section titled “RDLC import”- 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.
Website
Section titled “Website”The migration guide has the results for this version.
0.15.0 (2026-09-28)
Section titled “0.15.0 (2026-09-28)”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%.
Band margins
Section titled “Band margins”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.
RDLC import
Section titled “RDLC import”-
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.
Website
Section titled “Website”The migration guide has the results for this version.
0.14.0 (2026-09-28)
Section titled “0.14.0 (2026-09-28)”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%.
Charts
Section titled “Charts”- 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: counttakes any value, text too.
series: - { name: Price, value: "=Sum(Revenue) / Sum(Units)", format: C2 } - { name: Orders, value: =OrderId, aggregate: count }RDLC import
Section titled “RDLC import”- 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
Formataround a value is taken off, and text still left is read withToDouble. 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.
Website
Section titled “Website”The migration guide has the results for this version.
0.13.0 (2026-09-28)
Section titled “0.13.0 (2026-09-28)”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%.
Text as HTML
Section titled “Text as HTML”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 anddivs, lists and headings,font color, and the colour, weight, style and decoration of astyleattribute. 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.
Styles from the data
Section titled “Styles from the data”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 orrgb(); sizes in points or with a unit; weights asbold,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, andIndexOf(text, value).
Matrix values with aggregates
Section titled “Matrix values with aggregates”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)" }RDLC import
Section titled “RDLC import”- 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, andVariables!X.Valuereads 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
IIfcondition (RowNumber(Nothing) Mod 2),String.IsNullOrEmpty,InStr, andRunningValuewithCountDistinct.
- 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 oneSum.
Website
Section titled “Website”The fonts and styles reference documents styles from the data, and the migration guide has the results for this version.
0.12.0 (2026-09-27)
Section titled “0.12.0 (2026-09-27)”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%.
Cells that hold elements
Section titled “Cells that hold elements”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.
Counting different values
Section titled “Counting different values”A new aggregate: CountDistinct(Customer) counts the different values, skipping nulls, and takes a scope like the others.
RDLC import
Section titled “RDLC import”- 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.
CountDistinctconverts, except insideRunningValue.- 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.
Website
Section titled “Website”The migration guide now says how the converter is tested on about 6,000 public RDLC reports, with the results for this version.
0.11.0 (2026-09-26)
Section titled “0.11.0 (2026-09-26)”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%.
Groups inside tables
Section titled “Groups inside tables”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.
Detail rows
Section titled “Detail rows”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?.
RDLC import
Section titled “RDLC import”- 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
DataSetNamereads the report’s only dataset, as RDLC does.
0.10.0 (2026-09-26)
Section titled “0.10.0 (2026-09-26)”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.
Conditional styles
Section titled “Conditional styles”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.
RDLC import
Section titled “RDLC import”- Conditional styles: a style that picks among constants, such as
=IIf(Fields!Amount.Value < 0, "Red", "Black"), aSwitch, orIIf(RowNumber(Nothing) Mod 2, ...)stripes, becomeswhenrules. 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.
0.9.0 (2026-09-26)
Section titled “0.9.0 (2026-09-26)”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.
Checked against public RDLC reports
Section titled “Checked against public RDLC reports”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%.
Fixed in the engine
Section titled “Fixed in the engine”- 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 asdandoare unchanged.
New function
Section titled “New function”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').
RDLC import
Section titled “RDLC import”- 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), andx Is Nothing/x IsNot Nothingare converted.- A chart of type Shape without a subtype is a pie, RDL’s default. It used to be a placeholder.
- Nullable field types (
- 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.
Excel export
Section titled “Excel export”- 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.
0.8.0 (2026-09-25)
Section titled “0.8.0 (2026-09-25)”Tagua 0.8.0 draws a table’s own border, background and padding, framing all its rows.
Table frames
Section titled “Table frames”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.
Website
Section titled “Website”The site now has a changelog listing every release.
0.7.0 (2026-09-25)
Section titled “0.7.0 (2026-09-25)”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, and Windows font names
Section titled “Times, and Windows font names”- 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 Romanandserifmap onto Times, andCourier Newonto Courier, asArialalready 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.
Fonts in tagua import
Section titled “Fonts in tagua import”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.
0.6.0 (2026-09-25)
Section titled “0.6.0 (2026-09-25)”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.
Rows before a table’s data
Section titled “Rows before a table’s data”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
beforerows don’t. - Empty tables: the
beforerows 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.
Importing SSRS and ReportViewer reports
Section titled “Importing SSRS and ReportViewer reports”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.
0.5.0 (2026-09-25)
Section titled “0.5.0 (2026-09-25)”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.
Running totals
Section titled “Running totals”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
Sumand default to the same one. In a group’s detail they start again with each group, andRunningSum(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.
Importing SSRS and ReportViewer reports
Section titled “Importing SSRS and ReportViewer reports”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.
0.4.0 (2026-09-25)
Section titled “0.4.0 (2026-09-25)”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.
Matrices
Section titled “Matrices”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 bycolumns. Groups appear in the order their first row does, sosortorders them. - Values: each of
valuesis aggregated where a row group meets a column group:sum(the default),avg,count,minormax, 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
totalgets a labelled total of its instances. Totals work down and across: subtotals per group, total columns, and the grand total. - Styles:
headerStylestyles the column headers andtotalStylethe 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.
Importing SSRS matrices
Section titled “Importing SSRS matrices”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.
Website
Section titled “Website”taguareports.com now has a pricing page and a contact page, for licences and for help migrating RDLC, SSRS or Crystal Reports.
0.3.0 (2026-09-25)
Section titled “0.3.0 (2026-09-25)”Tagua 0.3.0 adds hyphenation: justified and narrow columns can break long words at the syllables their language allows.
Hyphenation
Section titled “Hyphenation”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’slanguage, 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.
Licences
Section titled “Licences”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.
0.2.0 (2026-09-24)
Section titled “0.2.0 (2026-09-24)”Tagua 0.2.0 adds Excel output: the same template and data that make the PDF now make an .xlsx workbook.
Excel export (new package: Tagua.Excel)
Section titled “Excel export (new package: Tagua.Excel)”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: coverandnonecropping. - 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.
Change in behaviour
Section titled “Change in behaviour”- Number formats without a precision now give 2 decimals on every system. This covers
N,FandPwithout 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)
Documentation
Section titled “Documentation”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.
0.1.0 (2026-09-24)
Section titled “0.1.0 (2026-09-24)”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
taguacommand (Tagua.Cli): render a template to PDF, with--watchto preview as you edit. - Native AOT compatible throughout.