using System.Text; using XFESystem.Desktop; namespace XFESystem.Browser; public sealed partial class BrowserService { private Func? operationCancellation; internal void SetOperationCancellation(Func? requested) => operationCancellation = requested; private bool OperationCancelled => operationCancellation?.Invoke() == true; private void CheckOperationCancellation() { if (OperationCancelled) throw new OperationCanceledException("The editor operation was cancelled at a resource/script boundary."); } /// Read real model and rendered view state. This neither calls /// render(), fabricates tokens nor discards the remaining browser tasks. internal BrowserDisplayList? ReadyEditorLayout(string source, string path, int width, int height) { CheckOperationCancellation(); if (ReadEditorText() != source || runtime is null || document is null) return null; string language = path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ? "csharp" : "javascript"; if (runtime.ReadString("xfeMonacoEditor.getModel().getLanguageId()") != language) return null; var lines = document.QuerySelectorAll(".view-lines .view-line"); if (lines.Length == 0) return null; var expected = source.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'); if (lines.Length > expected.Length) return null; bool visibleSource = false, renderedToken = false; for (int index = 0; index < lines.Length; index++) { // Monaco expands tabs/space placeholders in the view. Compare the // actual token characters, while the full model readback above // separately validates every original character and line ending. string actual = ViewCharacters(lines[index].TextContent); string original = ViewCharacters(expected[index]); if (actual != original) return null; if (actual.Length != 0) visibleSource = true; if (lines[index].QuerySelectorAll("span[class]").Any(span => span.ClassList.Any(name => name.StartsWith("mtk", StringComparison.Ordinal)))) renderedToken = true; } if (!string.IsNullOrWhiteSpace(source) && (!visibleSource || !renderedToken)) return null; var layout = Layout(new(width, height)); var viewport = new BrowserRect(0, 0, width, height); bool textPaint = layout.Commands.Any(command => command.Kind == BrowserDrawKind.Text && command.Opacity > 0 && !string.IsNullOrWhiteSpace(command.Text) && command.Node?.ParentElement?.Closest(".view-line") is not null && command.Bounds.Intersect(viewport) is { Width: > 0, Height: > 0 }); if (!string.IsNullOrWhiteSpace(source) && !textPaint) return null; return layout; } private static string ViewCharacters(string text) { var value = new StringBuilder(text.Length); foreach (char character in text) if (character is not (' ' or '\t' or '\u00a0' or '\u200b')) value.Append(character); return value.ToString(); } }