Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tests/Filee.App.Tests/RenderTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Renders real windows headlessly (Skia) to catch XAML / binding errors and to produce screenshots
// in bin/.../screenshots for visual review.

using System.Globalization;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Headless;
Expand Down Expand Up @@ -1608,6 +1609,7 @@ public void Preset_editor_keeps_rotation_visible_for_pdf_and_drops_it_for_target
public void Preset_editor_saves_every_frame_only_for_png_and_jpg()
{
TestServices.EnsureInitialized("en");
using var culture = new CultureScope("en-US"); // "100,000": numbers follow the regional format
var loc = AppHost.Get<Filee.Core.Localization.ILocalizer>();

var preset = new Filee.Core.Presets.Preset { TargetFormat = "jpg" };
Expand Down Expand Up @@ -1635,6 +1637,7 @@ public void Preset_editor_saves_every_frame_only_for_png_and_jpg()
public void Preset_editor_shows_playback_speed_only_for_targets_that_keep_animations()
{
TestServices.EnsureInitialized("ko");
using var culture = new CultureScope("ko-KR"); // "0.25×": numbers follow the regional format
var loc = AppHost.Get<Filee.Core.Localization.ILocalizer>();
var preset = new Filee.Core.Presets.Preset { TargetFormat = "gif", Name = "Slow GIF" };
var vm = new PresetEditorViewModel(preset, loc);
Expand Down Expand Up @@ -2788,6 +2791,16 @@ internal static void Pump()
}
}

/// <summary>The regional format until disposed: numbers in labels follow it, not the UI language.</summary>
private sealed class CultureScope : IDisposable
{
private readonly CultureInfo _before = CultureInfo.CurrentCulture;

public CultureScope(string name) => CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(name);

public void Dispose() => CultureInfo.CurrentCulture = _before;
}

internal static void Save(TopLevel window, string name)
{
// Disposed at the end, so the frame stays alive while Skia encodes it: a collection in between (the idle memory
Expand Down
13 changes: 7 additions & 6 deletions tests/Filee.Engines.Tests/OfdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// with the frame and labels, values on the page, QR code and seal pictures), a WPS document (page size only in
// Document.xml, decimal colours, wrapped body text over two pages), and hostile packages.

using System.Globalization;
using System.IO.Compression;
using System.Text;
using System.Xml.Linq;
Expand Down Expand Up @@ -65,10 +66,10 @@ private static string MakeInvoice(string folder)
var template = new StringBuilder();
void Label(string text, double x, double y, double size = 3)
{
template.Append($"""<ofd:TextObject ID="1" Boundary="{x} {y - size} 60 {size + 1}" Font="3" Size="{size}"><ofd:TextCode X="0" Y="{size}">{text}</ofd:TextCode></ofd:TextObject>""");
template.Append(string.Create(CultureInfo.InvariantCulture, $"""<ofd:TextObject ID="1" Boundary="{x} {y - size} 60 {size + 1}" Font="3" Size="{size}"><ofd:TextCode X="0" Y="{size}">{text}</ofd:TextCode></ofd:TextObject>"""));
}
void Rule(double x0, double y0, double x1, double y1) =>
template.Append($"""<ofd:PathObject ID="1" Boundary="{x0} {y0} {Math.Max(1, x1 - x0)} {Math.Max(1, y1 - y0)}"><ofd:AbbreviatedData>M 0 0 L {x1 - x0} {y1 - y0}</ofd:AbbreviatedData></ofd:PathObject>""");
template.Append(string.Create(CultureInfo.InvariantCulture, $"""<ofd:PathObject ID="1" Boundary="{x0} {y0} {Math.Max(1, x1 - x0)} {Math.Max(1, y1 - y0)}"><ofd:AbbreviatedData>M 0 0 L {x1 - x0} {y1 - y0}</ofd:AbbreviatedData></ofd:PathObject>"""));
Label("电子发票(普通发票)", 75, 12, 6);
Label("发票号码:", 140, 18);
Label("开票日期:", 140, 24);
Expand Down Expand Up @@ -112,7 +113,7 @@ void Rule(double x0, double y0, double x1, double y1) =>

var page = new StringBuilder();
void Value(string text, double x, double y, int font = 4, double size = 3.5, string extra = "") =>
page.Append($"""<ofd:TextObject ID="1" Boundary="{x} {y - size} 80 {size + 1}" Font="{font}" Size="{size}"><ofd:FillColor Value="0 0 0"/><ofd:TextCode X="0" Y="{size}"{extra}>{text}</ofd:TextCode></ofd:TextObject>""");
page.Append(string.Create(CultureInfo.InvariantCulture, $"""<ofd:TextObject ID="1" Boundary="{x} {y - size} 80 {size + 1}" Font="{font}" Size="{size}"><ofd:FillColor Value="0 0 0"/><ofd:TextCode X="0" Y="{size}"{extra}>{text}</ofd:TextCode></ofd:TextObject>"""));
page.Append("""<ofd:ImageObject ID="1" ResourceID="7" Boundary="8 6 20 20" CTM="20 0 0 20 0 0"/>""");
Value("25442000000123456789", 155.5, 18, 5, extra: " DeltaX=\"g 19 2.1\"");
Value("2025年03月18日", 155.5, 24);
Expand Down Expand Up @@ -260,7 +261,7 @@ private static string MakeWps(string folder)
Text(zip, "Doc_0/Res.xml", $"""<?xml version="1.0" encoding="utf-8"?><ofd:Res {Ns}><ofd:Fonts><ofd:Font ID="3" FontName="仿宋_GB2312"/><ofd:Font ID="4" FontName="黑体"/></ofd:Fonts></ofd:Res>""");

string Line(string text, double x, double y, int font = 3, double size = 5.6, string extra = "") =>
$"""<ofd:TextObject ID="1" Font="{font}" Size="{size}" Boundary="{x} {y - size} 160 {size * 1.3}"{extra}><ofd:FillColor Value="5 7 59"/><ofd:TextCode X="0" Y="{size}">{text}</ofd:TextCode></ofd:TextObject>""";
string.Create(CultureInfo.InvariantCulture, $"""<ofd:TextObject ID="1" Font="{font}" Size="{size}" Boundary="{x} {y - size} 160 {size * 1.3}"{extra}><ofd:FillColor Value="5 7 59"/><ofd:TextCode X="0" Y="{size}">{text}</ofd:TextCode></ofd:TextObject>""");
var first = new StringBuilder();
first.Append(Line("医院信息化建设方案", 67.15, 40, 4, 8.5, " Weight=\"700\""));
// A paragraph of three lines on a 156.8 mm measure (28 characters): the first indented by two characters.
Expand Down Expand Up @@ -334,7 +335,7 @@ private static string MakeOfd(string folder, string page, string template = "",
}

private static string TextObject(string text, double x, double y, double size = 3.175, int font = 3, string code = "", double width = 60) =>
$"""<ofd:TextObject ID="1" Boundary="{x} {y - size} {width} {size + 1}" Font="{font}" Size="{size}"><ofd:TextCode X="0" Y="{size}"{code}>{text}</ofd:TextCode></ofd:TextObject>""";
string.Create(CultureInfo.InvariantCulture, $"""<ofd:TextObject ID="1" Boundary="{x} {y - size} {width} {size + 1}" Font="{font}" Size="{size}"><ofd:TextCode X="0" Y="{size}"{code}>{text}</ofd:TextCode></ofd:TextObject>""");

[Fact]
public void A_block_drawn_by_one_text_code_keeps_its_lines_and_a_short_delta_list_goes_on()
Expand Down Expand Up @@ -509,7 +510,7 @@ public async Task Columns_of_a_list_keep_their_rows_level_and_the_page_its_heigh
{
var template = new StringBuilder();
void Rule(double x0, double y0, double x1, double y1) =>
template.Append($"""<ofd:PathObject ID="1" Boundary="{x0} {y0} {Math.Max(1, x1 - x0)} {Math.Max(1, y1 - y0)}"><ofd:AbbreviatedData>M 0 0 L {x1 - x0} {y1 - y0}</ofd:AbbreviatedData></ofd:PathObject>""");
template.Append(string.Create(CultureInfo.InvariantCulture, $"""<ofd:PathObject ID="1" Boundary="{x0} {y0} {Math.Max(1, x1 - x0)} {Math.Max(1, y1 - y0)}"><ofd:AbbreviatedData>M 0 0 L {x1 - x0} {y1 - y0}</ofd:AbbreviatedData></ofd:PathObject>"""));
Rule(5, 30, 205, 30);
Rule(5, 37, 205, 37);
Rule(5, 110, 205, 110);
Expand Down
22 changes: 15 additions & 7 deletions tests/Filee.Engines.Tests/SpreadsheetFormatTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System.IO.Compression;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Filee.Core.Conversion;
using Filee.Core.Presets;
Expand Down Expand Up @@ -598,22 +599,29 @@ public async Task LibreOffice_reads_our_ods_and_we_read_its_ods_and_xls()
foreach (var (name, book) in books)
{
var sales = book.Sheets[0];
// An ODS by LibreOffice keeps the text Calc showed (text:p), with the separators of Calc's locale
// ("1.350.000원", "12,8%" on a German system): compare those without the separators.
Func<string, string> shown = name == "ODS by LibreOffice" ? WithoutSeparators : text => text;
Assert.True(new[] { "매출", "메모", "넓은 표" }.SequenceEqual(book.Sheets.Select(s => s.Name)), name);
Assert.True(sales.Get(2, 1)!.Text == "1,350,000원", $"{name}: {sales.Get(2, 1)!.Text}");
Assert.True(sales.Get(2, 3)!.Text == "12.8%", $"{name}: {sales.Get(2, 3)!.Text}");
Assert.True(shown(sales.Get(2, 1)!.Text) == shown("1,350,000원"), $"{name}: {sales.Get(2, 1)!.Text}");
Assert.True(shown(sales.Get(2, 3)!.Text) == shown("12.8%"), $"{name}: {sales.Get(2, 3)!.Text}");
// LibreOffice shows built-in dates in its own locale's order: compare the value.
Assert.True(sales.Get(2, 2)!.Value == October1, $"{name}: {sales.Get(2, 2)!.Value}");
Assert.True(sales.Get(4, 1)!.Text == "2,220,000원", $"{name}: {sales.Get(4, 1)!.Text}");
Assert.True(shown(sales.Get(4, 1)!.Text) == shown("2,220,000원"), $"{name}: {sales.Get(4, 1)!.Text}");
Assert.True(new CellRange(0, 0, 0, 3) == Assert.Single(sales.Merges), name);
Assert.True(sales.Get(1, 0)!.Style.Align == CellAlign.Center, name);
// LibreOffice measures character widths with its own fonts, so widths through ODS move by up to a fifth;
// XLS keeps Excel's own unit.
var tolerance = name.StartsWith("XLS", StringComparison.Ordinal) ? 0.01 : 0.25;
Assert.True(Math.Abs(sales.ColumnWidth(0) - 14) <= 14 * tolerance, $"{name}: {sales.ColumnWidth(0)}");
// LibreOffice measures character widths with its own fonts, so widths it writes move by up to a fifth. XLS
// keeps Excel's unit, but for Korean text LibreOffice measures with its locale's default East Asian font
// (Microsoft YaHei on a German system: 15.27 for 14). The ratio of the columns stays.
Assert.True(Math.Abs(sales.ColumnWidth(0) - 14) <= 14 * 0.25, $"{name}: {sales.ColumnWidth(0)}");
Assert.True(Math.Abs(sales.ColumnWidth(0) / sales.ColumnWidth(1) - 14.0 / 12) < 0.02, $"{name}: {sales.ColumnWidth(0)} / {sales.ColumnWidth(1)}");
}
}

/// <summary>"1.350.000원" and "1,350,000원" as "1350000원", "12,8 %" and "12.8%" as "12.8%".</summary>
private static string WithoutSeparators(string text) =>
Regex.Replace(Regex.Replace(text, @"(?<=\d)[.,\u00A0\u202F' ](?=\d{3}(?!\d))", ""), @"(?<=\d),(?=\d)", ".").Replace(" %", "%");

private async Task<string> LibreOfficeAsync(IConverter libreOffice, string input, string from, string to)
{
var output = Path.Combine(Path.GetDirectoryName(input)!, $"{Path.GetFileNameWithoutExtension(input)}.lo-{from}.{to}");
Expand Down
Loading