├── .editorconfig
├── .gitignore
├── AvRichTextBox.sln
├── AvRichTextBox
├── .gitignore
├── AvRichTextBox.csproj
├── Blocks
│ ├── Block.cs
│ ├── Paragraph.cs
│ └── Table.cs
├── EditableBlocks
│ ├── EditableParagraph
│ │ ├── EditableParagraph.cs
│ │ ├── EditableParagraph_EventHandlers.cs
│ │ └── EditableParagraph_Methods.cs
│ ├── EditableTable
│ │ └── EditableTable.cs
│ └── IEditableBlock.cs
├── Editables
│ ├── EditableInlineUIContainer.cs
│ ├── EditableLineBreak.cs
│ ├── EditableRun.cs
│ └── IEditable.cs
├── FlowDocument
│ ├── FlowDocument.cs
│ ├── FlowDocument_Debugger.cs
│ ├── FlowDocument_EditingFunctions.cs
│ ├── FlowDocument_Formatting.cs
│ ├── FlowDocument_InlineManipulation.cs
│ ├── FlowDocument_Insert-Delete.cs
│ ├── FlowDocument_LoadSave.cs
│ ├── FlowDocument_RangeXaml.cs
│ ├── FlowDocument_SelectionExtend.cs
│ └── FlowDocument_SelectionMove.cs
├── LICENSE.txt
├── README.md
├── RequestExtensions.cs
├── RichTextBox
│ ├── RichTextBox.axaml
│ ├── RichTextBox.axaml.cs
│ ├── RichTextBoxInputClient.cs
│ ├── RichTextBox_DocEditing.cs
│ ├── RichTextBox_KeyDown.cs
│ ├── RichTextBox_PointerEvents.cs
│ ├── RichTextBox_RunFormatting.cs
│ └── RichTextBox_SelectionRect.cs
├── RichTextBoxViewModel
│ └── RichTextBoxViewModel.cs
├── RtfDomParserAv.dll
├── SaveAndLoadFormats
│ ├── HtmlConversions
│ │ ├── ContentToHtml.cs
│ │ └── HtmlToContent.cs
│ ├── RtfConversions
│ │ ├── ContentToRtf.cs
│ │ └── RtfToContent.cs
│ ├── WordImportExport
│ │ ├── CreateFlowDocument
│ │ │ ├── GetFlowDocument.cs
│ │ │ ├── GetParagraph.cs
│ │ │ ├── GetRun.cs
│ │ │ └── GetTable.cs
│ │ ├── CreateWordDocument
│ │ │ ├── CreateWordDrawing.cs
│ │ │ ├── CreateWordParagraph.cs
│ │ │ ├── CreateWordRun.cs
│ │ │ ├── CreateWordTable.cs
│ │ │ └── WordDocumentCreator.cs
│ │ └── HelperMethods.cs
│ └── Xaml
│ │ ├── FlowDocFromXamlString.cs
│ │ ├── XamlPackageData
│ │ ├── .rels
│ │ └── [Content_Types].xml
│ │ └── XamlStringFromFlowDoc.cs
├── TextRange
│ └── TextRange.cs
├── Undo
│ ├── IUndo.cs
│ └── Undos.cs
├── UniqueBitmap.cs
└── VisualHelper.cs
├── DemoApp_AvRichtextBox
├── App.axaml
├── App.axaml.cs
├── Assets
│ └── avalonia-logo.ico
├── DemoApp_AvRichTextBox.csproj
├── NumericSpinner
│ ├── NumericSpinner.axaml
│ └── NumericSpinner.axaml.cs
├── Program.cs
├── TestFiles
│ ├── TestDocumentRtf.rtf
│ ├── TestDocumentWord.docx
│ ├── TestDocumentWord.html
│ └── TestDocumentXamlPackage.xamlp
├── ViewLocator.cs
├── ViewModels
│ ├── MainWindowViewModel.cs
│ └── ViewModelBase.cs
├── Views
│ ├── MainWindow.axaml
│ ├── MainWindow.axaml.cs
│ └── MainWindow_File.xaml.cs
└── app.manifest
└── Directory.Build.props
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # IDE0220: Add explicit cast
4 | dotnet_style_prefer_foreach_explicit_cast_in_source = always
5 |
6 | # IDE0220: Add explicit cast
7 | dotnet_diagnostic.IDE0220.severity = none
8 | csharp_using_directive_placement = outside_namespace:silent
9 | csharp_prefer_simple_using_statement = true:suggestion
10 | csharp_prefer_braces = true:silent
11 | csharp_style_namespace_declarations = block_scoped:silent
12 | csharp_style_prefer_method_group_conversion = true:silent
13 | csharp_style_prefer_top_level_statements = true:silent
14 | csharp_style_prefer_primary_constructors = true:suggestion
15 | csharp_prefer_system_threading_lock = true:suggestion
16 | csharp_style_expression_bodied_methods = false:silent
17 | csharp_style_expression_bodied_constructors = false:silent
18 | csharp_style_expression_bodied_operators = false:silent
19 | csharp_style_expression_bodied_properties = true:silent
20 | csharp_style_expression_bodied_indexers = true:silent
21 | csharp_style_expression_bodied_accessors = true:silent
22 | csharp_style_expression_bodied_lambdas = true:silent
23 | csharp_style_expression_bodied_local_functions = false:silent
24 | csharp_indent_labels = no_change
25 |
26 | [*.{cs,vb}]
27 | #### Naming styles ####
28 |
29 | # Naming rules
30 |
31 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
32 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
33 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
34 |
35 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
36 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
37 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
38 |
39 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
40 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
41 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
42 |
43 | # Symbol specifications
44 |
45 | dotnet_naming_symbols.interface.applicable_kinds = interface
46 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
47 | dotnet_naming_symbols.interface.required_modifiers =
48 |
49 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
50 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
51 | dotnet_naming_symbols.types.required_modifiers =
52 |
53 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
54 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
55 | dotnet_naming_symbols.non_field_members.required_modifiers =
56 |
57 | # Naming styles
58 |
59 | dotnet_naming_style.begins_with_i.required_prefix = I
60 | dotnet_naming_style.begins_with_i.required_suffix =
61 | dotnet_naming_style.begins_with_i.word_separator =
62 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
63 |
64 | dotnet_naming_style.pascal_case.required_prefix =
65 | dotnet_naming_style.pascal_case.required_suffix =
66 | dotnet_naming_style.pascal_case.word_separator =
67 | dotnet_naming_style.pascal_case.capitalization = pascal_case
68 |
69 | dotnet_naming_style.pascal_case.required_prefix =
70 | dotnet_naming_style.pascal_case.required_suffix =
71 | dotnet_naming_style.pascal_case.word_separator =
72 | dotnet_naming_style.pascal_case.capitalization = pascal_case
73 | dotnet_style_coalesce_expression = true:suggestion
74 | dotnet_style_null_propagation = true:suggestion
75 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
76 | dotnet_style_prefer_auto_properties = true:silent
77 | dotnet_style_object_initializer = true:suggestion
78 | dotnet_style_collection_initializer = true:suggestion
79 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
80 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
81 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
82 | tab_width = 3
83 | indent_size = 3
84 | end_of_line = crlf
85 | dotnet_style_prefer_conditional_expression_over_return = true:silent
86 |
--------------------------------------------------------------------------------
/AvRichTextBox.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.8.34330.188
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvRichTextBox", "AvRichTextBox\AvRichTextBox.csproj", "{13845574-3E03-4CFC-8F3E-A076EBC750DE}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvRTB_App.Desktop", "AvRTB_App.Desktop\AvRTB_App.Desktop.csproj", "{E4E264C7-494F-4B81-820D-F5A8738886AC}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvRTB_NugetTesting", "AvRTB_NugetTesting\AvRTB_NugetTesting.csproj", "{C1F95367-C38D-4048-A390-A2E0E02B3030}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{33B80D73-F48F-4CA1-8077-D1AFFBEC773B}"
13 | ProjectSection(SolutionItems) = preProject
14 | .editorconfig = .editorconfig
15 | EndProjectSection
16 | EndProject
17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoApp_AvRichTextBox", "DemoApp_AvRichtextBox\DemoApp_AvRichTextBox.csproj", "{ACDA0832-4647-4EB4-BA3A-C3EC71C56900}"
18 | EndProject
19 | Global
20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
21 | Debug|Any CPU = Debug|Any CPU
22 | Release|Any CPU = Release|Any CPU
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {13845574-3E03-4CFC-8F3E-A076EBC750DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {13845574-3E03-4CFC-8F3E-A076EBC750DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {13845574-3E03-4CFC-8F3E-A076EBC750DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {13845574-3E03-4CFC-8F3E-A076EBC750DE}.Release|Any CPU.Build.0 = Release|Any CPU
29 | {E4E264C7-494F-4B81-820D-F5A8738886AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {E4E264C7-494F-4B81-820D-F5A8738886AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {E4E264C7-494F-4B81-820D-F5A8738886AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {E4E264C7-494F-4B81-820D-F5A8738886AC}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {C1F95367-C38D-4048-A390-A2E0E02B3030}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {C1F95367-C38D-4048-A390-A2E0E02B3030}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {C1F95367-C38D-4048-A390-A2E0E02B3030}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {C1F95367-C38D-4048-A390-A2E0E02B3030}.Release|Any CPU.Build.0 = Release|Any CPU
37 | {ACDA0832-4647-4EB4-BA3A-C3EC71C56900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {ACDA0832-4647-4EB4-BA3A-C3EC71C56900}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {ACDA0832-4647-4EB4-BA3A-C3EC71C56900}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {ACDA0832-4647-4EB4-BA3A-C3EC71C56900}.Release|Any CPU.Build.0 = Release|Any CPU
41 | EndGlobalSection
42 | GlobalSection(SolutionProperties) = preSolution
43 | HideSolutionNode = FALSE
44 | EndGlobalSection
45 | GlobalSection(ExtensibilityGlobals) = postSolution
46 | SolutionGuid = {D289C971-2F8E-40AE-A616-CCB56DC9D517}
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/AvRichTextBox/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | BatchFiles/
4 | Assets/
5 | AvRichTextBox.csproj.vscb.json
6 |
--------------------------------------------------------------------------------
/AvRichTextBox/AvRichTextBox.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | latest
7 |
8 |
9 | Simplectoplasm
10 | ---
11 | AvRichTextBox
12 | Simplecto.Avalonia.RichTextBox
13 | 1.3.5
14 | A RichTextBox control to be used in Avalonia
15 | Avalonia;UserControl;RichTextBox
16 | A RichTextBox control for Avalonia
17 | README.md
18 | MIT
19 |
20 | https://github.com/cuikp/AvRichTextBox
21 | git
22 | ver 1.3.5: Fixed Rtf line height divide-by-zero save error, and changed referenced RtfDomParser to RtfDomParserAv
23 |
24 | false
25 | false
26 | D:\PROG\LocalNuget\AvRichTextBox
27 | 1.2.5
28 |
29 |
30 |
31 |
32 | true
33 |
34 |
35 |
36 | false
37 |
38 |
39 |
40 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | RtfDomParserAv.dll
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | %(Filename)
86 |
87 |
88 | Code
89 | %(Filename)
90 |
91 |
92 | %(Filename)
93 |
94 |
95 | %(Filename)
96 |
97 |
98 | %(Filename)
99 |
100 |
101 | %(Filename)
102 |
103 |
104 | %(Filename)
105 |
106 |
107 | %(Filename)
108 |
109 |
110 | %(Filename)
111 |
112 |
113 | %(Filename)
114 |
115 |
116 | %(Filename)
117 |
118 |
119 |
120 |
121 |
122 | True
123 | \
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/AvRichTextBox/Blocks/Block.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using System;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Reactive.Linq;
7 | using System.Runtime.CompilerServices;
8 | using System.Threading.Tasks;
9 |
10 | namespace AvRichTextBox;
11 |
12 | public class Block : INotifyPropertyChanged
13 | {
14 | public event PropertyChangedEventHandler? PropertyChanged;
15 | public void NotifyPropertyChanged([CallerMemberName] String propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
16 |
17 | private Thickness _Margin = new (0, 0, 0, 0);
18 | public Thickness Margin { get => _Margin; set { _Margin = value; NotifyPropertyChanged(nameof(Margin)); } }
19 |
20 | public string Text
21 | {
22 | get
23 | {
24 | string returnText = "";
25 |
26 | switch (this.GetType())
27 | {
28 | case Type t when t == typeof(Paragraph):
29 | returnText = string.Join("", ((Paragraph)this).Inlines.ToList().ConvertAll(ied => ied.InlineText));
30 | break;
31 | //case Type t when t == typeof(Table):
32 | // returnText = "$";
33 | // break;
34 | }
35 | return returnText;
36 | }
37 | }
38 |
39 | public bool IsParagraph => this.GetType() == typeof(Paragraph);
40 | //public bool IsTable => this.GetType() == typeof(Table);
41 |
42 | internal int SelectionLength => SelectionEndInBlock - SelectionStartInBlock;
43 | public int BlockLength => this.IsParagraph ? ((Paragraph)this).Inlines.ToList().Sum(il => il.InlineLength) + 1 : 1; //Add one for paragraph itself
44 |
45 | private int _StartInDoc = 0;
46 | internal int StartInDoc
47 | {
48 | get => _StartInDoc;
49 | set { if (_StartInDoc != value) { _StartInDoc = value; NotifyPropertyChanged(nameof(StartInDoc)); } }
50 | }
51 |
52 | internal int EndInDoc => StartInDoc + BlockLength;
53 |
54 | private int _SelectionStartInBlock;
55 | public int SelectionStartInBlock
56 | {
57 | get => _SelectionStartInBlock;
58 | set { if (_SelectionStartInBlock != value) { _SelectionStartInBlock = value; NotifyPropertyChanged(nameof(SelectionStartInBlock)); } }
59 | }
60 |
61 | private int _SelectionEndInBlock;
62 | public int SelectionEndInBlock
63 | {
64 | get => _SelectionEndInBlock;
65 | set
66 | {
67 |
68 | if (_SelectionEndInBlock != value)
69 | {
70 | _SelectionEndInBlock = value; // Set the correct value
71 | NotifyPropertyChanged(nameof(SelectionEndInBlock));
72 | }
73 |
74 | }
75 |
76 | }
77 |
78 |
79 | public static bool IsFocusable => false;
80 |
81 | internal void ClearSelection() { this.SelectionStartInBlock = 0; this.SelectionEndInBlock = 0; }
82 | internal void CollapseToStart() { if (SelectionStartInBlock != SelectionEndInBlock) SelectionEndInBlock = SelectionStartInBlock; }
83 | internal void CollapseToEnd() { if (SelectionStartInBlock != SelectionEndInBlock) SelectionStartInBlock = SelectionEndInBlock; }
84 |
85 |
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/AvRichTextBox/Blocks/Table.cs:
--------------------------------------------------------------------------------
1 | //using Avalonia;
2 | //using Avalonia.Controls;
3 | //using Avalonia.Media;
4 | //using System.Collections.ObjectModel;
5 | //using System.ComponentModel;
6 | //using System.Runtime.CompilerServices;
7 |
8 | //namespace AvRichTextBox;
9 |
10 | //public partial class Table : Block, INotifyPropertyChanged
11 | //{
12 | // public new event PropertyChangedEventHandler? PropertyChanged;
13 | // private new void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
14 |
15 | // public Thickness BorderThickness => new Thickness(1);
16 |
17 | // public ObservableCollection Cells { get; set; } = [];
18 | // public ColumnDefinitions ColDefs { get; set; } = [];
19 | // public RowDefinitions RowDefs { get; set; } = [];
20 |
21 |
22 | // public Table(int noCols, int noRows, double TableWidth)
23 | // {
24 | // double eqSpacedColWidth = 10D / (double)noCols;
25 |
26 | // for (int rowno = 0; rowno < noRows; rowno++)
27 | // this.RowDefs.Add(new RowDefinition());
28 | // for (int colno = 0; colno < noCols; colno++)
29 | // this.ColDefs.Add(new ColumnDefinition(eqSpacedColWidth, GridUnitType.Star));
30 |
31 | // for (int rowno = 0; rowno < noRows; rowno++)
32 | // {
33 | // for (int colno = 0; colno < noCols; colno++)
34 | // {
35 | // Border cellborder = new() { BorderBrush = Brushes.Black, BorderThickness = new Thickness(0.5) };
36 | // SelectableTextBlock seltb = new() { Text = "this is some text, with wrapping as well." };
37 | // seltb.Background = Brushes.White;
38 | // seltb.TextWrapping = TextWrapping.Wrap;
39 | // cellborder.Child = seltb;
40 | // Cells.Add(cellborder);
41 | // Grid.SetRow(cellborder, rowno);
42 | // Grid.SetColumn(cellborder, colno);
43 | // }
44 | // }
45 |
46 |
47 | // }
48 |
49 |
50 |
51 |
52 |
53 | //}
54 |
55 |
--------------------------------------------------------------------------------
/AvRichTextBox/EditableBlocks/EditableParagraph/EditableParagraph.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Documents;
4 | using Avalonia.Input;
5 | using Avalonia.Interactivity;
6 | using Avalonia.Media;
7 | using Avalonia.Threading;
8 | using System;
9 | using System.Diagnostics;
10 | using System.Linq;
11 |
12 | namespace AvRichTextBox;
13 |
14 | public partial class EditableParagraph : SelectableTextBlock
15 | {
16 |
17 |
18 | public bool IsEditable { get; set; } = true;
19 |
20 | private readonly SolidColorBrush cursorBrush = new (Colors.Cyan, 0.55);
21 |
22 | public int RectCharacterIndex = 0;
23 |
24 | public EditableParagraph()
25 | {
26 | this.SelectionBrush = cursorBrush;
27 |
28 | this.Loaded += EditableParagraph_Loaded;
29 |
30 | this.PropertyChanged += EditableParagraph_PropertyChanged;
31 |
32 | //this.KeyDown += EditableParagraph_KeyDown;
33 |
34 | }
35 |
36 | private void EditableParagraph_Loaded(object? sender, RoutedEventArgs e)
37 | {
38 | UpdateInlines();
39 |
40 | }
41 |
42 | public static readonly StyledProperty TextLayoutInfoStartRequestedProperty = AvaloniaProperty.Register(nameof(TextLayoutInfoStartRequested));
43 | public bool TextLayoutInfoStartRequested { get => GetValue(TextLayoutInfoStartRequestedProperty); set { SetValue(TextLayoutInfoStartRequestedProperty, value); } }
44 |
45 | public static readonly StyledProperty TextLayoutInfoEndRequestedProperty = AvaloniaProperty.Register(nameof(TextLayoutInfoEndRequested));
46 | public bool TextLayoutInfoEndRequested { get => GetValue(TextLayoutInfoEndRequestedProperty); set { SetValue(TextLayoutInfoEndRequestedProperty, value); } }
47 |
48 | public void UpdateVMFromEPStart()
49 | {
50 | SelectionStartRect_Changed?.Invoke(this);
51 | this.SetValue(TextLayoutInfoStartRequestedProperty, false);
52 |
53 | }
54 |
55 | public void UpdateVMFromEPEnd()
56 | {
57 | SelectionEndRect_Changed?.Invoke(this);
58 | this.SetValue(TextLayoutInfoEndRequestedProperty, false);
59 | }
60 |
61 | //public void NotifyCharIndexRect(EditableParagraph ep, Rect selEndRect)
62 | //{
63 |
64 | // if (CharIndexRect_Notified != null)
65 | // CharIndexRect_Notified(ep, selEndRect);
66 | //}
67 |
68 |
69 | private void EditableParagraph_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
70 | {
71 |
72 | //Debug.WriteLine("e.propertyName = " + e.Property.Name);
73 |
74 | if (thisPar != null) //because this may be called right after paragraph has been deleted
75 | {
76 |
77 | switch (e.Property.Name)
78 | {
79 | case "Bounds":
80 | //Necessary for initial setting for each created paragraph
81 | if (thisPar != null)
82 | thisPar.FirstIndexLastLine = this.TextLayout.TextLines[^1].FirstTextSourceIndex;
83 | break;
84 |
85 | //case "Inlines":
86 | // UpdateVMFromEPStart();
87 | // UpdateVMFromEPEnd();
88 | // break;
89 |
90 | case "LineSpacing":
91 | this.UpdateLayout();
92 |
93 | if (TextLayout != null && TextLayout.TextLines.Count > 0)
94 | {
95 | double maxLineHeight = Math.Max(TextLayout.TextLines[0].Height, TextLayout.TextLines[^1].Height);
96 | thisPar.LineHeight = maxLineHeight;
97 | }
98 | //Debug.WriteLine("\nline spacing changed: LINESpacing = " + this.LineSpacing);
99 |
100 | break;
101 |
102 | case "SelectionStart":
103 | UpdateVMFromEPStart();
104 | break;
105 |
106 | case "SelectionEnd":
107 | UpdateVMFromEPEnd();
108 | break;
109 |
110 | case "SelectedText":
111 | thisPar.UpdateUIContainersSelected();
112 |
113 | break;
114 |
115 | case "TextLayoutInfoStartRequested":
116 | this.SetValue(TextLayoutInfoStartRequestedProperty, false);
117 | if (thisPar == null)
118 | Dispatcher.UIThread.Post(() => UpdateVMFromEPStart(), DispatcherPriority.Background);
119 | else
120 | UpdateVMFromEPStart();
121 | break;
122 |
123 | case "TextLayoutInfoEndRequested":
124 | this.SetValue(TextLayoutInfoEndRequestedProperty, false);
125 | if (thisPar == null)
126 | Dispatcher.UIThread.Post(() => UpdateVMFromEPEnd(), DispatcherPriority.Background);
127 | else
128 | UpdateVMFromEPEnd();
129 | break;
130 | }
131 |
132 | }
133 |
134 | }
135 |
136 | protected override void OnPointerPressed(PointerPressedEventArgs e)
137 | {
138 | //Prevent default behavior
139 |
140 |
141 | }
142 |
143 | protected override void OnKeyDown(KeyEventArgs e)
144 | {
145 | //Keep to override
146 | //Debug.WriteLine("thisEP focused = " + this.IsFocused);
147 | //if (!this.IsFocused)
148 | // e.Handled = true;
149 | //UpdateVMFromEPEnd();
150 | }
151 |
152 | protected override void OnLostFocus(RoutedEventArgs e)
153 | {
154 | base.OnLostFocus(e);
155 | this.Focusable = false;
156 | }
157 |
158 | protected override void OnGotFocus(GotFocusEventArgs e)
159 | {
160 | base.OnGotFocus(e);
161 | this.Focusable = true;
162 | }
163 |
164 |
165 | protected override void OnPointerMoved(PointerEventArgs e)
166 | {
167 | //e.Handled = true;
168 |
169 | TextHitTestResult result = this.TextLayout.HitTestPoint(e.GetPosition(this));
170 |
171 | MouseMove?.Invoke(this, result.TextPosition);
172 |
173 | }
174 |
175 | protected override void OnPointerReleased(PointerReleasedEventArgs e)
176 | {
177 | // Prevent default behavior
178 | //e.Handled = true;
179 | }
180 |
181 | internal void UpdateInlines()
182 | {
183 |
184 | if (((Paragraph)this.DataContext!).Inlines != null)
185 | this.Inlines = GetFormattedInlines();
186 |
187 | //foreach (Inline thisIL in this.Inlines!)
188 | // Debug.WriteLine("1:\n" + ((Run)thisIL).Text + " ::: " + thisIL.FontWeight);
189 |
190 | //this.Height = this.Inlines[0].get
191 |
192 |
193 | this.InvalidateMeasure();
194 | this.InvalidateVisual();
195 |
196 | }
197 |
198 | public int SelectionLength => SelectionEnd - SelectionStart;
199 |
200 | Paragraph? thisPar => this.DataContext as Paragraph;
201 |
202 |
203 | public new string Text => string.Join("", ((Paragraph)this.DataContext!).Inlines.ToList().ConvertAll(edinline => edinline.InlineText));
204 |
205 |
206 | }
207 |
208 |
--------------------------------------------------------------------------------
/AvRichTextBox/EditableBlocks/EditableParagraph/EditableParagraph_EventHandlers.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using System.Collections.Generic;
3 |
4 | namespace AvRichTextBox;
5 |
6 | public partial class EditableParagraph
7 | {
8 | public delegate void MouseMoveHandler(EditableParagraph sender, int charIndex);
9 | public event MouseMoveHandler? MouseMove;
10 |
11 | public delegate void SelectionStartRectChangedHandler(EditableParagraph sender);
12 | public event SelectionStartRectChangedHandler? SelectionStartRect_Changed;
13 |
14 | public delegate void SelectionEndRectChangedHandler(EditableParagraph sender);
15 | public event SelectionEndRectChangedHandler? SelectionEndRect_Changed;
16 |
17 | //public delegate void TextChangedHandler(EditableParagraph sender);
18 | //public event TextChangedHandler? EditableParagraph_TextChanged;
19 |
20 | //public delegate void CharIndexRectNotifiedHandler(EditableParagraph sender, Rect selEndRect);
21 | //public event CharIndexRectNotifiedHandler? CharIndexRect_Notified;
22 |
23 | //public delegate void KeyDownHandler(EditableParagraph sender, double hitPositionFromLeft);
24 | //public new event KeyDownHandler? KeyDown;
25 |
26 |
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/AvRichTextBox/EditableBlocks/EditableParagraph/EditableParagraph_Methods.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls.Documents;
2 | using Avalonia.Media;
3 | using System;
4 | using System.Diagnostics;
5 |
6 | namespace AvRichTextBox;
7 |
8 | public partial class EditableParagraph
9 | {
10 |
11 | private InlineCollection GetFormattedInlines()
12 | {
13 |
14 | InlineCollection returnInlines = [];
15 | foreach (IEditable ied in ((Paragraph)this.DataContext!).Inlines)
16 | returnInlines.Add(ied.BaseInline);
17 |
18 | return returnInlines;
19 |
20 | }
21 |
22 |
23 | private int GetClosestIndex(int lineNo, double distanceFromLeft, int direction)
24 | {
25 | CharacterHit chit = this.TextLayout.TextLines[lineNo + direction].GetCharacterHitFromDistance(distanceFromLeft);
26 |
27 | double CharDistanceDiffThis = Math.Abs(distanceFromLeft - this.TextLayout.HitTestTextPosition(chit.FirstCharacterIndex).Left);
28 | double CharDistanceDiffNext = Math.Abs(distanceFromLeft - this.TextLayout.HitTestTextPosition(chit.FirstCharacterIndex + 1).Left);
29 |
30 | if (CharDistanceDiffThis > CharDistanceDiffNext)
31 | return chit.FirstCharacterIndex + 1;
32 | else
33 | return chit.FirstCharacterIndex;
34 |
35 |
36 | }
37 |
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/AvRichTextBox/EditableBlocks/EditableTable/EditableTable.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Interactivity;
4 | using System.Collections.ObjectModel;
5 |
6 | namespace AvRichTextBox;
7 |
8 |
9 | public partial class EditableTable : Grid, IEditableBlock
10 | {
11 | ////public bool IsEditable { get; set; } = true;
12 | ////SolidColorBrush cursorBrush = new SolidColorBrush(Colors.Cyan, 0.55);
13 |
14 | //public static readonly StyledProperty> CellsProperty = AvaloniaProperty.Register>(nameof(Cells));
15 | //public static readonly StyledProperty ColDefsProperty = AvaloniaProperty.Register(nameof(ColDefs));
16 | //public static readonly StyledProperty RowDefsProperty = AvaloniaProperty.Register(nameof(RowDefs));
17 |
18 | //public ColumnDefinitions ColDefs
19 | //{
20 | // get => GetValue(ColDefsProperty);
21 | // set { SetValue(ColDefsProperty, value); }
22 | //}
23 |
24 | //public RowDefinitions RowDefs
25 | //{
26 | // get => GetValue(RowDefsProperty);
27 | // set { SetValue(RowDefsProperty, value); }
28 | //}
29 |
30 | //public ObservableCollection Cells
31 | //{
32 | // get => GetValue(CellsProperty);
33 | // set => SetValue(CellsProperty, value);
34 | //}
35 |
36 | //public EditableTable()
37 | //{
38 | // this.Loaded += EditableTable_Loaded;
39 |
40 | //}
41 |
42 | //private void Cells_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
43 | //{
44 | // foreach (Border newCell in e.NewItems)
45 | // this.Children.Add(newCell);
46 | //}
47 |
48 | //private void ColDefs_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
49 | //{
50 | // foreach (ColumnDefinition cdef in e.NewItems)
51 | // ColumnDefinitions.Add(cdef);
52 | // foreach (ColumnDefinition cdef in e.OldItems)
53 | // ColumnDefinitions.Remove(cdef);
54 |
55 | //}
56 |
57 | //private void RowDefs_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
58 | //{
59 | // foreach (RowDefinition rdef in e.NewItems)
60 | // RowDefinitions.Add(rdef);
61 | // foreach (RowDefinition rdef in e.OldItems)
62 | // RowDefinitions.Remove(rdef);
63 |
64 | //}
65 |
66 | //private void EditableTable_Loaded(object? sender, RoutedEventArgs e)
67 | //{
68 | // //ColDefs.CollectionChanged += ColDefs_CollectionChanged;
69 | // //RowDefs.CollectionChanged += RowDefs_CollectionChanged;
70 | // Cells.CollectionChanged += Cells_CollectionChanged;
71 |
72 | // RowDefinitions = RowDefs;
73 | // ColumnDefinitions = ColDefs;
74 |
75 | // foreach (Border b in Cells)
76 | // {
77 | // this.Children.Add(b);
78 | // //Debug.WriteLine(Grid.GetRow(b));
79 | // }
80 |
81 | // //Debug.WriteLine("cellcount=" + Cells.Count);
82 |
83 | // this.UpdateLayout();
84 | //}
85 |
86 | ////public string Text => string.Join("", ((Table)this.DataContext).Inlines.ToList().ConvertAll(edinline => edinline.InlineText));
87 |
88 |
89 |
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/AvRichTextBox/EditableBlocks/IEditableBlock.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Data;
4 | using Avalonia.Input;
5 | using Avalonia.Interactivity;
6 | using Avalonia.Media;
7 | using System.Diagnostics;
8 | using System.Linq;
9 |
10 | namespace AvRichTextBox;
11 |
12 | public interface IEditableBlock
13 | {
14 |
15 |
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/AvRichTextBox/Editables/EditableInlineUIContainer.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Documents;
4 | using Avalonia.Media;
5 | using System.ComponentModel;
6 | using System.Runtime.CompilerServices;
7 |
8 | namespace AvRichTextBox;
9 |
10 | public class EditableInlineUIContainer : InlineUIContainer, IEditable, INotifyPropertyChanged
11 | {
12 | public new event PropertyChangedEventHandler? PropertyChanged;
13 | private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
14 |
15 | public EditableInlineUIContainer(Control c) { Child = c; }
16 |
17 | public Inline BaseInline => this;
18 | public Paragraph? myParagraph { get; set; }
19 | public int TextPositionOfInlineInParagraph { get; set; }
20 | public string InlineText { get; set; } = "@";
21 | public string DisplayInlineText { get => " => " + (this.Child != null && this.Child.GetType() == typeof(Image) ? "Image" : "NoChild"); }
22 | public string FontName => "---";
23 | public int InlineLength => 1;
24 | public bool IsEmpty => false;
25 | public bool IsLastInlineOfParagraph { get; set; }
26 | //public double InlineHeight => (this.Child != null && this.Child.GetType() == typeof(Image) ? : this.Child.Bounds.Height;
27 | public double InlineHeight => Child == null ? 0 : this.Child.Bounds.Height;
28 |
29 |
30 | public int ImageNo;
31 |
32 | public IEditable Clone() { return new EditableInlineUIContainer(this.Child){ myParagraph = this.myParagraph }; }
33 |
34 | //for DebuggerPanel
35 | private bool _IsStartInline = false;
36 | public bool IsStartInline { get => _IsStartInline; set { _IsStartInline = value; NotifyPropertyChanged(nameof(BackBrush)); NotifyPropertyChanged(nameof(InlineSelectedBorderThickness)); } }
37 | private bool _IsEndInline = false;
38 | public bool IsEndInline { get => _IsEndInline; set { _IsEndInline = value; NotifyPropertyChanged(nameof(BackBrush)); } }
39 | private bool _IsWithinSelectionInline = false;
40 | public bool IsWithinSelectionInline { get => _IsWithinSelectionInline; set { _IsWithinSelectionInline = value; NotifyPropertyChanged(nameof(BackBrush)); } }
41 | public Thickness InlineSelectedBorderThickness => (IsStartInline || IsEndInline) ? new Thickness(3) : new Thickness(1);
42 |
43 | public SolidColorBrush BackBrush
44 | {
45 | get
46 | {
47 | if (IsStartInline) return new SolidColorBrush(Colors.LawnGreen);
48 | else if (IsEndInline) return new SolidColorBrush(Colors.Pink);
49 | else if (IsWithinSelectionInline) return new SolidColorBrush(Colors.LightGray);
50 | else return new SolidColorBrush(Colors.Transparent);
51 | }
52 | }
53 |
54 | public string InlineToolTip => "";
55 |
56 | private bool _IsSelected = false;
57 | public bool IsSelected { get => _IsSelected; set { _IsSelected = value; this.Child.Opacity = value ? 0.2 : 1; } }
58 |
59 | }
60 |
61 |
62 |
--------------------------------------------------------------------------------
/AvRichTextBox/Editables/EditableLineBreak.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Documents;
4 | using Avalonia.Media;
5 | using System.ComponentModel;
6 | using System.Runtime.CompilerServices;
7 |
8 | namespace AvRichTextBox;
9 |
10 | public class EditableLineBreak : LineBreak, IEditable, INotifyPropertyChanged
11 | {
12 | public new event PropertyChangedEventHandler? PropertyChanged;
13 | private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
14 |
15 | public EditableLineBreak() { }
16 |
17 | public Inline BaseInline => this;
18 | public Paragraph? myParagraph { get; set; }
19 | public int TextPositionOfInlineInParagraph { get; set; }
20 | public string InlineText { get; set; } = @"\v"; //make literal to count as 2 characters
21 | //public string InlineText { get; set; } = "\v";
22 | public string DisplayInlineText => "{>LINEBREAK<}";
23 | public int InlineLength => 2; //because LineBreak acts as a double character in TextBlock? - anyway don't use LineBreak, use \v instead
24 | public double InlineHeight => FontSize;
25 | public string FontName => "---";
26 | public bool IsEmpty => false;
27 | public bool IsLastInlineOfParagraph { get; set; }
28 |
29 | public IEditable Clone() => new EditableLineBreak() { myParagraph = this.myParagraph };
30 |
31 |
32 | //for DebuggerPanel
33 | private bool _IsStartInline = false;
34 | public bool IsStartInline { get => _IsStartInline; set { _IsStartInline = value; NotifyPropertyChanged(nameof(BackBrush)); NotifyPropertyChanged(nameof(InlineSelectedBorderThickness)); } }
35 | private bool _IsEndInline = false;
36 | public bool IsEndInline { get => _IsEndInline; set { _IsEndInline = value; NotifyPropertyChanged(nameof(BackBrush)); NotifyPropertyChanged(nameof(InlineSelectedBorderThickness)); } }
37 | private bool _IsWithinSelectionInline = false;
38 | public bool IsWithinSelectionInline { get => _IsWithinSelectionInline; set { _IsWithinSelectionInline = value; NotifyPropertyChanged(nameof(BackBrush)); } }
39 | public Thickness InlineSelectedBorderThickness => (IsStartInline || IsEndInline) ? new Thickness(3) : new Thickness(1);
40 |
41 | public SolidColorBrush BackBrush
42 | {
43 | get
44 | {
45 | if (IsStartInline) return new SolidColorBrush(Colors.LawnGreen);
46 | else if (IsEndInline) return new SolidColorBrush(Colors.Pink);
47 | else if (IsWithinSelectionInline) return new SolidColorBrush(Colors.LightGray);
48 | else return new SolidColorBrush(Colors.Transparent);
49 | }
50 | }
51 |
52 | public string InlineToolTip => "";
53 |
54 |
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/AvRichTextBox/Editables/EditableRun.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.Documents;
3 | using Avalonia.Media;
4 | using System.ComponentModel;
5 | using System.Runtime.CompilerServices;
6 |
7 | namespace AvRichTextBox;
8 |
9 | public class EditableRun : Run, IEditable, INotifyPropertyChanged
10 | {
11 | public new event PropertyChangedEventHandler? PropertyChanged;
12 | private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
13 |
14 | public EditableRun() { }
15 |
16 | public EditableRun(string text)
17 | {
18 | this.Text = text;
19 | //FontFamily = "Meiryo";
20 | FontSize = 16;
21 | BaselineAlignment = BaselineAlignment.Baseline;
22 | }
23 |
24 | public Inline BaseInline => this;
25 | public Paragraph? myParagraph { get; set; }
26 | public int TextPositionOfInlineInParagraph { get; set; }
27 | public string InlineText { get => Text!; set => Text = value; }
28 | public string DisplayInlineText
29 | {
30 | get => IsEmpty ? "{>EMPTY<}" : (InlineText.Length == 1 ? Text!.Replace(" ", "{>SPACE<}").Replace("\t", "{>TAB<}") : Text!.Replace("\t", "{>TAB<}"));
31 | }
32 |
33 | public int InlineLength => InlineText.Length;
34 | public double InlineHeight => FontSize;
35 | public bool IsEmpty => InlineText.Length == 0;
36 | public string FontName => FontFamily?.Name == null ? "" : FontFamily?.Name!;
37 |
38 | public bool IsLastInlineOfParagraph { get; set; }
39 |
40 | public IEditable Clone()
41 | {
42 | return new EditableRun(this.Text!)
43 | {
44 | FontStyle = this.FontStyle,
45 | FontWeight = this.FontWeight,
46 | TextDecorations = this.TextDecorations,
47 | FontSize = this.FontSize,
48 | FontFamily = this.FontFamily,
49 | Background = this.Background,
50 | myParagraph = this.myParagraph,
51 | TextPositionOfInlineInParagraph = this.TextPositionOfInlineInParagraph, //necessary because clone is produced when calculating range inline positions
52 | IsLastInlineOfParagraph = this.IsLastInlineOfParagraph,
53 | BaselineAlignment = this.BaselineAlignment,
54 | Foreground = this.Foreground,
55 | };
56 | }
57 |
58 | //For DebuggerPanel
59 | private bool _IsStartInline = false;
60 | public bool IsStartInline { get => _IsStartInline; set { _IsStartInline = value; NotifyPropertyChanged(nameof(BackBrush)); NotifyPropertyChanged(nameof(InlineSelectedBorderThickness)); } }
61 | private bool _IsEndInline = false;
62 | public bool IsEndInline { get => _IsEndInline; set { _IsEndInline = value; NotifyPropertyChanged(nameof(BackBrush)); NotifyPropertyChanged(nameof(InlineSelectedBorderThickness)); } }
63 | private bool _IsWithinSelectionInline = false;
64 | public bool IsWithinSelectionInline { get => _IsWithinSelectionInline; set { _IsWithinSelectionInline = value; NotifyPropertyChanged(nameof(BackBrush)); } }
65 | public Thickness InlineSelectedBorderThickness => (IsStartInline || IsEndInline) ? new Thickness(3) : new Thickness(0.7);
66 |
67 | public SolidColorBrush BackBrush
68 | {
69 | get
70 | {
71 | if (IsStartInline) return new SolidColorBrush(Colors.LawnGreen);
72 | else if (IsEndInline) return new SolidColorBrush(Colors.Pink);
73 | else if (IsWithinSelectionInline) return new SolidColorBrush(Colors.LightGray);
74 | else return new SolidColorBrush(Colors.Transparent);
75 | }
76 | }
77 |
78 | public string InlineToolTip => $"Background: {Background}\nForeground: {Foreground}\nFontFamily: {FontFamily}";
79 |
80 |
81 | }
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/AvRichTextBox/Editables/IEditable.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.Documents;
3 | using Avalonia.Media;
4 | using System.ComponentModel;
5 |
6 | namespace AvRichTextBox;
7 |
8 | public interface IEditable : INotifyPropertyChanged
9 | {
10 |
11 | Inline BaseInline { get; }
12 |
13 | internal Paragraph? myParagraph { get; set; }
14 |
15 | internal bool IsStartInline { get; set; }
16 | internal bool IsEndInline { get; set; }
17 | internal bool IsWithinSelectionInline { get; set; }
18 | internal bool IsLastInlineOfParagraph { get; set; }
19 | internal int TextPositionOfInlineInParagraph { get; set; }
20 | internal int GetCharPosInInline(int charPos) => charPos - myParagraph!.StartInDoc - TextPositionOfInlineInParagraph;
21 | public string InlineText { get; set; }
22 | public bool IsEmpty { get; }
23 | public int InlineLength { get; }
24 | public double InlineHeight { get; }
25 | public IEditable Clone();
26 | public bool IsRun => this.GetType() == typeof(EditableRun);
27 | public bool IsUIContainer => this.GetType() == typeof(EditableInlineUIContainer);
28 | public bool IsLineBreak => this.GetType() == typeof(EditableLineBreak);
29 |
30 | public Thickness InlineSelectedBorderThickness { get; }
31 | public SolidColorBrush BackBrush { get; }
32 | public string DisplayInlineText { get; }
33 | public string InlineToolTip { get; }
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/AvRichTextBox/FlowDocument/FlowDocument_Debugger.cs:
--------------------------------------------------------------------------------
1 | using DynamicData;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Reactive.Linq;
6 |
7 | namespace AvRichTextBox;
8 |
9 | public partial class FlowDocument
10 | {
11 |
12 | internal bool ShowDebugger = false;
13 | internal ObservableCollection SelectionParagraphs { get; set; } = []; // for DebuggerPanel
14 |
15 | private void UpdateDebuggerSelectionParagraphs()
16 | {
17 |
18 | #if DEBUG
19 |
20 | SelectionParagraphs.Clear();
21 | SelectionParagraphs.AddRange(Blocks.Where(p => p.StartInDoc + p.BlockLength > Selection.Start && p.StartInDoc <= Selection.End).ToList().ConvertAll(bb => (Paragraph)bb));
22 |
23 | //Visuals for DebuggerPanel
24 | foreach (Paragraph p in SelectionParagraphs)
25 | {
26 | foreach (IEditable ied in p.Inlines)
27 | {
28 | IEditable startInline = Selection.GetStartInline();
29 | IEditable endInline = Selection.GetEndInline();
30 | int thisRunIndex = p.Inlines.IndexOf(ied);
31 | ied.IsStartInline = ied == startInline;
32 | ied.IsEndInline = ied == endInline;
33 | ied.IsWithinSelectionInline =
34 | startInline != null && endInline != null && thisRunIndex > p.Inlines.IndexOf(startInline) && thisRunIndex < p.Inlines.IndexOf(endInline);
35 | }
36 | }
37 |
38 | #endif
39 |
40 | }
41 |
42 |
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/AvRichTextBox/FlowDocument/FlowDocument_EditingFunctions.cs:
--------------------------------------------------------------------------------
1 | using DynamicData;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text.RegularExpressions;
7 |
8 | namespace AvRichTextBox;
9 |
10 | public partial class FlowDocument
11 | {
12 |
13 | private Dictionary> KeepParsAndInlines(TextRange tRange)
14 | {
15 | Dictionary> returnDict = [];
16 |
17 | List allBlocks = GetRangeBlocks(tRange);
18 | foreach (Block b in allBlocks)
19 | {
20 | List inlines = [];
21 | if (b is Paragraph p)
22 | inlines.AddRange(p.Inlines.ToList().ConvertAll(il => il.Clone()));
23 | returnDict.TryAdd(b, inlines);
24 |
25 | }
26 |
27 | return returnDict;
28 |
29 | }
30 |
31 | internal int SetRangeToInlines(TextRange tRange, List newInlines)
32 | { // All of this should constitute one Undo operation
33 | //Debug.WriteLine("newinlines=\n" + string.Join("\n", newInlines.ConvertAll(il => il.InlineText)));
34 |
35 | int addedCharCount = 0;
36 |
37 | Paragraph startPar = tRange.StartParagraph;
38 |
39 | int rangeStart = tRange.Start;
40 | int deleteRangeLength = tRange.Length;
41 | int parIndex = Blocks.IndexOf(startPar);
42 |
43 | Undos.Add(new PasteUndo(KeepParsAndInlines(tRange), parIndex, this, rangeStart, deleteRangeLength - newInlines.Sum(nil=>nil.InlineLength)));
44 |
45 | //Delete selected range first
46 | if (tRange.Length > 0)
47 | DeleteRange(tRange, false);
48 |
49 | IEditable startInline = tRange.GetStartInline();
50 | List splitInlines = SplitRunAtPos(tRange.Start, startInline, startInline.GetCharPosInInline(tRange.Start));
51 |
52 | int insertionPt = startPar.Inlines.IndexOf(splitInlines[0]) + 1;
53 |
54 | int startInlineIndex = startPar.Inlines.IndexOf(splitInlines[0]) + 1;
55 | Paragraph addPar = startPar;
56 | int inlineno = 0;
57 | foreach (IEditable newinline in newInlines)
58 | {
59 | inlineno++;
60 |
61 | bool addnewpar = false;
62 |
63 | if (newinline.InlineText.EndsWith('\r'))
64 | {
65 | newinline.InlineText = newinline.InlineText[..^1];
66 | addnewpar = inlineno > 1;
67 | //Debug.WriteLine("addnew par? " + addnewpar + " (" + newinline.InlineText + ")");
68 | }
69 |
70 | if (addnewpar)
71 | {
72 | List moveInlines = addPar.Inlines.Take(new Range(0, startInlineIndex)).ToList(); // create an independent new list
73 | addPar.Inlines.RemoveMany(moveInlines);
74 |
75 | //Create new paragraph to insert
76 | addPar = new Paragraph();
77 | addPar.Inlines.AddRange(moveInlines);
78 | startInlineIndex = addPar.Inlines.Count;
79 | Blocks.Insert(parIndex, addPar);
80 | addPar.CallRequestInlinesUpdate();
81 | UpdateBlockAndInlineStarts(addPar);
82 | addedCharCount += 1;
83 | }
84 |
85 | addPar.Inlines.Insert(startInlineIndex, newinline);
86 | addPar.CallRequestInlinesUpdate();
87 | UpdateBlockAndInlineStarts(addPar);
88 | addedCharCount += newinline.InlineLength;
89 | }
90 |
91 | if (splitInlines[0].InlineText == "")
92 | startPar.Inlines.Remove(splitInlines[0]);
93 |
94 | startPar.CallRequestInlinesUpdate();
95 | UpdateBlockAndInlineStarts(startPar);
96 |
97 | return addedCharCount;
98 |
99 | }
100 |
101 |
102 | internal void SetRangeToText(TextRange tRange, string newText)
103 | { //The delete range and SetRangeToText should constitute one Undo operation
104 |
105 | Paragraph startPar = tRange.StartParagraph;
106 | int rangeStart = tRange.Start;
107 | int deleteRangeLength = tRange.Length;
108 | int parIndex = Blocks.IndexOf(startPar);
109 |
110 | Undos.Add(new PasteUndo(KeepParsAndInlines(tRange), parIndex, this, rangeStart, deleteRangeLength - newText.Length));
111 |
112 | //Delete any selected text first
113 | if (tRange.Length > 0)
114 | {
115 | DeleteRange(tRange, false);
116 | tRange.CollapseToStart();
117 | SelectionExtendMode = ExtendMode.ExtendModeNone;
118 | }
119 |
120 | IEditable startInline = tRange.GetStartInline();
121 | List splitInlines = SplitRunAtPos(tRange.Start, startInline, startInline.GetCharPosInInline(tRange.Start));
122 |
123 | int startInlineIndex = startPar.Inlines.IndexOf(splitInlines[0]) + 1;
124 |
125 | EditableRun? sRun = splitInlines[0] as EditableRun;
126 | EditableRun newEditableRun = new(newText)
127 | {
128 | FontFamily = sRun!.FontFamily,
129 | FontWeight = sRun.FontWeight,
130 | FontStyle = sRun.FontStyle,
131 | FontSize = sRun.FontSize,
132 | TextDecorations = sRun.TextDecorations,
133 | Background = sRun.Background
134 | };
135 |
136 | startPar.Inlines.Insert(startInlineIndex, newEditableRun);
137 |
138 | if (splitInlines[0].InlineText == "")
139 | startPar.Inlines.Remove(splitInlines[0]);
140 |
141 | startPar.CallRequestInlinesUpdate();
142 | UpdateBlockAndInlineStarts(startPar);
143 |
144 | }
145 |
146 |
147 | internal void Undo()
148 | {
149 | if (Undos.Count > 0)
150 | {
151 | Undos.Last().PerformUndo();
152 |
153 | UpdateSelection();
154 |
155 | if (Undos.Last().UpdateTextRanges)
156 | UpdateTextRanges(Selection.Start, Undos.Last().UndoEditOffset);
157 |
158 | Undos.RemoveAt(Undos.Count - 1);
159 |
160 | if (ShowDebugger)
161 | UpdateDebuggerSelectionParagraphs();
162 |
163 | ScrollInDirection!(1);
164 | ScrollInDirection!(-1);
165 | }
166 | }
167 |
168 | internal void RestoreDeletedBlocks(Dictionary> parsAndInlines, int blockIndex)
169 | {
170 | //Reset all paragraphs with new inlines exactly as before
171 |
172 | foreach (KeyValuePair> restorePar in parsAndInlines)
173 | {
174 | if (restorePar.Key is Paragraph p)
175 | {
176 | p.Inlines.Clear();
177 | p.CallRequestInlinesUpdate();
178 | p.Inlines.AddRange(restorePar.Value);
179 | }
180 | }
181 |
182 | //Restore all of the previous paragraphs
183 | Blocks.RemoveAt(blockIndex);
184 | Blocks.AddOrInsertRange(parsAndInlines.ToList().ConvertAll(pil => pil.Key), blockIndex);
185 |
186 | foreach (KeyValuePair> restorePar in parsAndInlines)
187 | if (restorePar.Key.IsParagraph)
188 | {
189 | Paragraph? p = restorePar.Key as Paragraph;
190 | p!.CallRequestInlinesUpdate();
191 | p.ClearSelection();
192 | }
193 |
194 | UpdateBlockAndInlineStarts(blockIndex);
195 |
196 | }
197 |
198 | [GeneratedRegex(@"[\r\n]")]
199 | internal static partial Regex FindLineBreakCharsRegex();
200 |
201 |
202 | internal IEditable GetStartInline(int charIndex)
203 | {
204 |
205 | Paragraph? startPar = Blocks.LastOrDefault(b => b.IsParagraph && (b.StartInDoc <= charIndex))! as Paragraph;
206 |
207 | if (startPar != null)
208 | {
209 | //Check if start at end of last paragraph (cannot span from end of a paragraph)
210 | if (startPar != Blocks.Where(b => b.IsParagraph).Last() && startPar!.EndInDoc == charIndex)
211 | startPar = Blocks.FirstOrDefault(b => b.IsParagraph && Blocks.IndexOf(b) > Blocks.IndexOf(startPar))! as Paragraph;
212 | }
213 |
214 | if (startPar == null) return null!;
215 |
216 | IEditable startInline = null!;
217 | bool IsAtLineBreak = false;
218 |
219 | IEditable startInlineReal = startPar.Inlines.LastOrDefault(ied => startPar.StartInDoc + ied.TextPositionOfInlineInParagraph <= charIndex)!;
220 | startInline = startPar.Inlines.LastOrDefault(ied => !ied.IsLineBreak && startPar.StartInDoc + ied.TextPositionOfInlineInParagraph <= charIndex)!;
221 | IsAtLineBreak = startInline != startInlineReal;
222 |
223 | return startInline!;
224 |
225 | }
226 |
227 |
228 | }
--------------------------------------------------------------------------------
/AvRichTextBox/FlowDocument/FlowDocument_LoadSave.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml.Packaging;
2 | using System;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Text.RegularExpressions;
6 | using static AvRichTextBox.WordConversions;
7 | using static AvRichTextBox.RtfConversions;
8 | using static AvRichTextBox.XamlConversions;
9 | using RtfDomParserAv;
10 | using System.Text;
11 | using HtmlAgilityPack;
12 |
13 | namespace AvRichTextBox;
14 |
15 | public partial class FlowDocument
16 | {
17 | internal void LoadRtf(string rtfContent)
18 | {
19 | RTFDomDocument rtfdom = new();
20 | //rtfdom.Load(fileName);
21 |
22 | // Do this to fix malformed `\o "` and orphaned quotes
23 | if (rtfContent.Contains("\\o "))
24 | {
25 | rtfContent = rtfContent.Replace("\\o \"}", "\\o \"\"}").Replace(" \"}", " }");
26 | rtfContent = Regex.Replace(rtfContent, "\\\\o \".*?\"", "\\o\"\"");
27 | }
28 | using MemoryStream rtfStream = new(Encoding.UTF8.GetBytes(rtfContent));
29 | using StreamReader streamReader = new(rtfStream);
30 |
31 | rtfdom.Load(streamReader.BaseStream);
32 |
33 | try
34 | {
35 | ClearDocument();
36 | GetFlowDocumentFromRtf(rtfdom!, this);
37 | InitializeDocument();
38 | }
39 | catch (Exception ex2) { Debug.WriteLine($"error getting flow doc:\n{ex2.Message}"); }
40 | }
41 | internal void LoadRtfFromFile(string fileName)
42 | {
43 | try
44 | {
45 | string rtfContent = File.ReadAllText(fileName);
46 | LoadRtf(rtfContent);
47 | }
48 | catch (Exception ex3)
49 | {
50 | if (ex3.HResult == -2147024864)
51 | throw new IOException($"The file:\n{fileName}\ncannot be opened because it is currently in use by another application.", ex3);
52 | else
53 | Debug.WriteLine($"Error trying to open file: {ex3.Message}");
54 | }
55 | }
56 |
57 | internal void SaveRtfToFile(string fileName)
58 | {
59 | try
60 | {
61 | //string rtfText = GetRtfFromFlowDocumentBlocks(this.Blocks);
62 | string rtfText = SaveRtf();
63 | File.WriteAllText(fileName, rtfText, Encoding.Default);
64 | //Debug.WriteLine(rtfText);
65 | }
66 | catch (Exception ex2) { Debug.WriteLine($"error getting flow doc:\n{ex2.Message}"); }
67 | }
68 | internal string SaveRtf()
69 | {
70 | return GetRtfFromFlowDocument(this);
71 | }
72 |
73 |
74 | internal void SaveXamlToFile(string fileName)
75 | {
76 | File.WriteAllText(fileName, SaveXaml());
77 | }
78 |
79 | internal void LoadXamlFromFile(string fileName)
80 | {
81 | string xamlDocString = File.ReadAllText(fileName);
82 | LoadXaml(xamlDocString);
83 | }
84 |
85 | internal string SaveXaml()
86 | {
87 | return GetDocXaml(false, this);
88 | }
89 |
90 | internal void LoadXaml(string xamlContent)
91 | {
92 | ProcessXamlString(xamlContent, this);
93 | InitializeDocument();
94 | }
95 |
96 | internal void SaveHtmlDocToFile(string fileName)
97 | {
98 | HtmlDocument hdoc = HtmlConversions.GetHtmlFromFlowDocument(this);
99 | hdoc.Save(fileName);
100 | }
101 | internal string SaveHtml()
102 | {
103 | HtmlDocument hdoc = HtmlConversions.GetHtmlFromFlowDocument(this);
104 | return hdoc.DocumentNode.OuterHtml;
105 | }
106 |
107 | internal void LoadHtmlDocFromFile(string fileName)
108 | {
109 | try
110 | {
111 | LoadHtml(File.ReadAllText(fileName));
112 | }
113 | catch (Exception ex3)
114 | {
115 | if (ex3.HResult == -2147024864)
116 | throw new IOException($"The file:\n{fileName}\ncannot be opened because it is currently in use by another application.\n{ex3.Message}");
117 | else
118 | Debug.WriteLine($"Error trying to open file: {ex3.Message}");
119 | }
120 |
121 | }
122 | internal void LoadHtml(string htmlContent)
123 | {
124 | try
125 | {
126 | ClearDocument();
127 | HtmlDocument hdoc = new();
128 | hdoc.LoadHtml(htmlContent);
129 | HtmlConversions.GetFlowDocumentFromHtml(hdoc, this);
130 | InitializeDocument();
131 | }
132 | catch (Exception ex2) { Debug.WriteLine("error getting flow doc:\n" + ex2.Message); }
133 | }
134 |
135 |
136 | internal void SaveWordDocToFile(string fileName)
137 | {
138 | WordConversions.SaveWordDoc(fileName, this);
139 | }
140 |
141 | internal void LoadWordDocFromFile(string fileName)
142 | {
143 | try
144 | {
145 | using WordprocessingDocument WordDoc = WordprocessingDocument.Open(fileName, false);
146 | try
147 | {
148 | ClearDocument();
149 | GetFlowDocument(WordDoc.MainDocumentPart!, this);
150 | InitializeDocument();
151 | }
152 | catch (Exception ex2) { Debug.WriteLine("error getting flow doc:\n" + ex2.Message); }
153 | }
154 | catch (Exception ex3)
155 | {
156 | if (ex3.HResult == -2147024864)
157 | throw new IOException($"The file:\n{fileName}\ncannot be opened because it is currently in use by another application.", ex3);
158 | else
159 | Debug.WriteLine($"Error trying to open file: {ex3.Message}");
160 | }
161 |
162 | }
163 |
164 | internal void LoadXamlPackage(string fileName)
165 | {
166 |
167 | XamlConversions.LoadXamlPackage(fileName, this);
168 |
169 | InitializeDocument();
170 |
171 | }
172 |
173 |
174 | internal void SaveXamlPackage(string fileName)
175 | {
176 | XamlConversions.SaveXamlPackage(fileName, this);
177 | }
178 |
179 | }
180 |
181 |
182 |
--------------------------------------------------------------------------------
/AvRichTextBox/FlowDocument/FlowDocument_RangeXaml.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using static AvRichTextBox.XamlConversions;
3 | using System.Text;
4 |
5 |
6 | namespace AvRichTextBox;
7 |
8 | public partial class FlowDocument
9 | {
10 | public void SaveRangeToXamlStream(TextRange trange, Stream stream)
11 | {
12 | StringBuilder rangeXamlBuilder = new(SectionTextDefault);
13 | rangeXamlBuilder.Append(GetParagraphRunsXaml(CreateNewInlinesForRange(trange), false));
14 | rangeXamlBuilder.Append("");
15 | byte[] stringBytes = Encoding.UTF8.GetBytes(rangeXamlBuilder.ToString());
16 | stream.Write(stringBytes, 0, stringBytes.Length);
17 |
18 | }
19 |
20 | internal void LoadXamlStreamIntoRange (Stream stream, TextRange trange)
21 | {
22 | byte[] streamBytes = new byte[stream.Length];
23 | stream.Read(streamBytes, 0, streamBytes.Length);
24 | string xamlString = Encoding.UTF8.GetString(streamBytes, 0, streamBytes.Length);
25 | //ProcessXamlString(xamlString);
26 |
27 | }
28 |
29 | }
30 |
31 |
32 |
--------------------------------------------------------------------------------
/AvRichTextBox/FlowDocument/FlowDocument_SelectionExtend.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace AvRichTextBox;
5 |
6 | public partial class FlowDocument
7 | {
8 |
9 | internal void ExtendSelectionRight()
10 | {
11 | Selection.BiasForwardEnd = true;
12 |
13 | switch (SelectionExtendMode)
14 | {
15 | case ExtendMode.ExtendModeNone:
16 | case ExtendMode.ExtendModeRight:
17 |
18 | SelectionExtendMode = ExtendMode.ExtendModeRight;
19 |
20 | if (Selection.EndParagraph == Blocks[^1] && Selection.EndParagraph.SelectionEndInBlock == Selection.EndParagraph.Text.Length)
21 | return; // End of document
22 |
23 | Selection!.End += 1;
24 |
25 | break;
26 |
27 | case ExtendMode.ExtendModeLeft:
28 |
29 | Selection!.Start += 1;
30 | if (Selection.Start == Selection.End)
31 | SelectionExtendMode = ExtendMode.ExtendModeRight;
32 |
33 | break;
34 | }
35 |
36 | ScrollInDirection!(1);
37 |
38 | }
39 |
40 | internal void ExtendSelectionLeft()
41 | {
42 | Selection.BiasForwardEnd = false;
43 |
44 | switch (SelectionExtendMode)
45 | {
46 | case ExtendMode.ExtendModeNone:
47 | case ExtendMode.ExtendModeLeft:
48 | if (Selection!.Start == 0) return;
49 | Selection.Start -= 1;
50 | SelectionExtendMode = ExtendMode.ExtendModeLeft;
51 | break;
52 |
53 | case ExtendMode.ExtendModeRight:
54 | if (Selection!.End == 0) return;
55 | Selection!.End -= 1;
56 | if (Selection.Start == Selection.End)
57 | SelectionExtendMode = ExtendMode.ExtendModeLeft;
58 | break;
59 | }
60 |
61 | ScrollInDirection!(-1);
62 | }
63 |
64 | internal void ExtendSelectionDown()
65 | {
66 | Selection.BiasForwardEnd = true;
67 |
68 | switch (SelectionExtendMode)
69 | {
70 | case ExtendMode.ExtendModeNone:
71 | case ExtendMode.ExtendModeRight:
72 |
73 | SelectionExtendMode = ExtendMode.ExtendModeRight;
74 |
75 | if (Selection!.EndParagraph == Blocks[^1] && Selection.End == Text.Length)
76 | return; // last line of document
77 |
78 | Paragraph origEndPar = Selection.EndParagraph;
79 |
80 | int nextEnd = Selection.EndParagraph.StartInDoc + Selection.EndParagraph.CharNextLineEnd;
81 |
82 | if (Selection.EndParagraph.IsEndAtLastLine)
83 | {
84 | if (Selection.EndParagraph != Blocks[^1])
85 | {
86 | int nextParIndex = Blocks.IndexOf(Selection.EndParagraph) + 1;
87 | Paragraph nextPar = (Paragraph)Blocks[nextParIndex];
88 | int oldSE = Selection.End;
89 | Selection.End = Math.Min(nextPar.StartInDoc + nextPar.BlockLength - 1, nextEnd);
90 | }
91 | }
92 | else
93 | Selection.End = nextEnd;
94 |
95 |
96 | //for selection continuity
97 | if (Selection.EndParagraph != origEndPar)
98 | {
99 | origEndPar.SelectionEndInBlock = origEndPar.Text.Length;
100 | Selection.EndParagraph.SelectionStartInBlock = 0;
101 | }
102 |
103 | break;
104 |
105 | case ExtendMode.ExtendModeLeft:
106 |
107 | if (Selection!.StartParagraph == Blocks[^1] && Selection.StartParagraph.IsStartAtLastLine)
108 | return; // last line of document
109 |
110 | int newStart = Selection.StartParagraph.StartInDoc + Selection.StartParagraph.CharNextLineStart;
111 |
112 | if (Blocks.IndexOf(Selection!.StartParagraph) < Blocks.Count - 1)
113 | {
114 | Block nextBlock = Blocks[Blocks.IndexOf(Selection.StartParagraph) + 1];
115 | int charsFromStart = Selection.StartParagraph.SelectionStartInBlock - Selection.StartParagraph.FirstIndexLastLine;
116 | if (Selection.StartParagraph.IsStartAtLastLine)
117 | {
118 | newStart = nextBlock.StartInDoc + charsFromStart;
119 | Selection.StartParagraph.CollapseToStart();
120 | }
121 |
122 | }
123 |
124 | if (newStart > Selection.End)
125 | {
126 | int oldEnd = Selection.End;
127 | Selection.End = newStart;
128 | Selection.Start = oldEnd;
129 | SelectionExtendMode = ExtendMode.ExtendModeRight;
130 | }
131 | else
132 | Selection!.Start = newStart;
133 |
134 | break;
135 | }
136 |
137 | ScrollInDirection!(1);
138 |
139 | }
140 |
141 | internal void ExtendSelectionUp()
142 | {
143 | Paragraph? prevPar = null;
144 | Selection.BiasForwardEnd = false;
145 |
146 | switch (SelectionExtendMode)
147 | {
148 | case ExtendMode.ExtendModeNone:
149 | case ExtendMode.ExtendModeLeft:
150 |
151 | if (Selection!.StartParagraph == Blocks[0] && Selection.StartParagraph.IsStartAtFirstLine)
152 | return; // first line of document
153 |
154 | Paragraph origStartPar = Selection.StartParagraph;
155 | if (Selection.StartParagraph.IsStartAtFirstLine)
156 | {
157 | prevPar = (Paragraph)Blocks[Blocks.IndexOf(Selection.StartParagraph) - 1];
158 | Selection.Start = Math.Min(prevPar.StartInDoc + prevPar.BlockLength - 2, prevPar.StartInDoc + prevPar.FirstIndexLastLine + Selection.StartParagraph.CharPrevLineStart);
159 | }
160 | else
161 | Selection.Start = Selection.StartParagraph.StartInDoc + Selection.StartParagraph.CharPrevLineStart;
162 |
163 | //for selection continuity
164 | SelectionExtendMode = ExtendMode.ExtendModeLeft;
165 | if (Selection.StartParagraph != origStartPar)
166 | {
167 | origStartPar.SelectionStartInBlock = 0;
168 | Selection.StartParagraph.SelectionEndInBlock = Selection.StartParagraph.Text.Length;
169 | }
170 |
171 | break;
172 |
173 |
174 | case ExtendMode.ExtendModeRight:
175 |
176 | int newEnd = Selection.EndParagraph.StartInDoc + Selection.EndParagraph.CharPrevLineEnd;
177 |
178 | if (Blocks.IndexOf(Selection!.EndParagraph) > 0)
179 | {
180 | prevPar = (Paragraph)Blocks[Blocks.IndexOf(Selection.EndParagraph) - 1];
181 | int charsFromStart = Selection.EndParagraph.SelectionEndInBlock;
182 | if (Selection.EndParagraph.IsEndAtFirstLine)
183 | {
184 | newEnd = prevPar.StartInDoc + prevPar.FirstIndexLastLine + charsFromStart;
185 | Selection.EndParagraph.CollapseToStart();
186 | }
187 |
188 | }
189 |
190 | if (newEnd < Selection.Start)
191 | {
192 | int oldStart = Selection.Start;
193 | Selection.Start = newEnd;
194 | Selection.End = oldStart;
195 | SelectionExtendMode = ExtendMode.ExtendModeLeft;
196 | }
197 | else
198 | Selection!.End = newEnd;
199 |
200 | break;
201 | }
202 |
203 | ScrollInDirection!(-1);
204 |
205 |
206 | }
207 |
208 | internal void EnsureSelectionContinuity()
209 | {
210 |
211 | foreach (Paragraph p in Blocks.Where(p => !SelectionParagraphs.Contains(p)))
212 | p.ClearSelection();
213 |
214 | if (SelectionParagraphs.Count > 1)
215 | {
216 | SelectionParagraphs[0].SelectionEndInBlock = SelectionParagraphs[0].BlockLength;
217 |
218 | for (int i = Blocks.IndexOf(SelectionParagraphs[0]) + 1; i < Blocks.IndexOf(SelectionParagraphs[^1]); i++)
219 | {
220 | Blocks[i].SelectionStartInBlock = 0;
221 | Blocks[i].SelectionEndInBlock = Blocks[i].BlockLength;
222 | }
223 |
224 | SelectionParagraphs[^1].SelectionStartInBlock = 0;
225 | }
226 |
227 |
228 | ////Temp for debugging
229 | //foreach (Paragraph p in Blocks.Where(p => !SelectionParagraphs.Contains(p)))
230 | // p.Background = new SolidColorBrush(Colors.Transparent);
231 | //foreach (Paragraph p in Blocks.Where(p => SelectionParagraphs.Contains(p)))
232 | // p.Background = new SolidColorBrush(Colors.Red);
233 |
234 | }
235 |
236 | }
237 |
238 |
239 |
--------------------------------------------------------------------------------
/AvRichTextBox/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2024] [Simplecto]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/AvRichTextBox/README.md:
--------------------------------------------------------------------------------
1 | # A RichTextBox control for Avalonia
2 | [](https://www.nuget.org/packages/Simplecto.Avalonia.RichTextBox/)
3 |
4 | As of 2024, Avalonia doesn't yet come with a RichTextBox, and since I needed one I created a "poor-man's version" based on the existing control `SelectableTextBlock`.
5 |
6 | Mirroring WPF, this `RichTextBox` control uses the concept of a `FlowDocument` (`FlowDoc`), which contains `Blocks` (at the current time, only `Paragraph` is available, although `Section` or `Table` could be added later).
7 | `Paragraph` contains `IEditable` objects (`EditableRun` (from `Avalonia.Run`) and `EditableInlineUIContainer` (from `Avalonia.InlineUIContainer`)) and it is bound to an `EditableParagraph` (inheriting from `SelectableTextBlock`).
8 |
9 | The `FlowDoc` is at heart merely an `ObservableCollection` of Blocks bound as the `ItemsSource` of an `ItemsControl` inside a `ScrollViewer`. Upon adding the appropriate key input handling, the control functions like a `RichTextBox`.
10 |
11 | (The hard part after that was implementing the selection logic, because `Selection` for the `RichTextBox` has to be able to move between and span multiple Paragraphs (SelectableTextBlocks), both with the keyboard and the mouse, and to allow editing functions that involve splitting or merging Paragraphs. And of course the Inline logic for spanning, inserting, splitting or deleting Inlines.)
12 |
13 | ```mermaid
14 | classDiagram
15 | class RichTextBox{
16 | +FlowDocument FlowDoc
17 | }
18 | class FlowDocument{
19 | +ObservableCollection
20 | }
21 | class FlowDoc{
22 | -List TextRanges
23 | }
24 | class Blocks{
25 | +Paragraph Paragraph
26 | }
27 | class Paragraph{
28 | +IEditable Objects
29 | +EditableParagraph EditableParagraph
30 | }
31 | class IEditable{
32 | +EditableRun EditableRun
33 | +EditableInlineUIContainer EditableInlineUIContainer
34 | }
35 | class Selection{
36 | }
37 | class TextRange{
38 | +int Start
39 | +int End
40 | +int Length
41 | +Delete()
42 | +string Text
43 | +ApplyFormatting(AvaloniaProperty, object)
44 | }
45 |
46 | RichTextBox --> FlowDocument : has
47 | FlowDocument --> Blocks : has
48 | FlowDoc --> TextRange : has
49 | Blocks --> Paragraph : contains
50 | Paragraph --> IEditable : has
51 | TextRange --> Selection : instance
52 | RichTextBox --> FlowDoc : has
53 |
54 | ```
55 |
56 | **A Debugging panel can be displayed by setting "ShowDebuggerPanelInDebugMode" to True. The panel displays Inline debugging information - Inline starts, paragraph starts, inline texts, and indicates the inlines of the Selection start and end by background color coding. The Debugger panel is not shown in Release mode**
57 |
58 | The RichTextBox has the usual key functions:
59 | * Ctrl+B for **bold**/unbold
60 | * Ctrl-I for *italic*/unitalic
61 | * Ctrl-U for underline/remove underline
62 | * Ctrl-Z for undo
63 | * Ctrl-A for select all
64 |
65 | The `FlowDoc` has a `Selection` property, with `Start`, `End`, `Length`, `Select`, `Delete`, `Text`, etc.
66 |
67 | The `RichTextBox` also includes the concept of `TextRange` (of which `Selection` is merely a special case), which can be defined to format text from code independent from the current `FlowDoc.Selection`. A new `TextRange` is created with a `Start` and `End` (and its owning `FlowDoc`), whereby it is automatically added to the `FlowDoc's TextRanges List` so its Start and/or End can be updated whenever text changes in the `FlowDoc` require it. `TextRange` also has an `ApplyFormatting` property which allows any `AvaloniaProperty` to be applied that pertains to Inlines.
68 |
69 | The RichTextBox content can be saved/loaded either as straight Xaml or a XamlPackage (to preserve images), similar to the WPF RichTextBox.
70 | It can also save and load the FlowDoc content as a Word document (.docx), Rtf document (.rtf) or Html (.html), though only with a subset of attributes. This includes text, common text/paragraph formatting, images, highlighting, forecolor, justification, borders, etc.
71 |
72 |
73 | ## Various future to-do improvements include:
74 | * Word/Html/RTF export and import can be fleshed out (to support more attributes)
75 | * Save/Load Xaml, Rtf functionality (to/from a stream) for TextRanges
76 | * Adding Table support
77 | * Allow the Undo limit to be set, and create a Redo stack
78 | * Stress testing
79 |
80 | RtfDomParser used for parsing of rtf files can be found at https://github.com/SourceCodeBackup/RtfDomParser, but for this project I had to manually modify it to use Avalonia.Media instead of System.Drawing. Generation of .rtf is my own concoction with the bare minimum to produce a readable .rtf file/dataobject.
81 |
82 | **Added 2025/02/22:
83 | Internal binding was of the RTB itself to its viewmodel, which prevented external binding to UserControl properties (such as IsVisible). Internal binding is now to the immediate child (DockPanel "MainDP"), freeing up the properties of the UserControl itself.
84 | Also upgraded copy/paste to allow copying and pasting of paragraph breaks (\r), which were ignored before.
85 |
86 | **Added 2025/02/25**
87 | ver 1.0.16 now works with Avalonia 11.1.xx & 11.2.xx! Binding update issues resolved. Previous AvRichTextBox versions failed on Avalonia 11.1 and higher and have been deprecated.
88 | In addition, added IME support for Chinese/Japanese input. Kanji and Hanzi can now be directly inputted in the RichTextBox.
89 |
90 | **Added 2025/02/26**
91 | ver 1.0.17 improves IME popup location and behavior (Hides on Esc key, or after backspacing to null entry).
92 | In addition, the RichTextBox content can now be saved as .rtf (SaveRtfDoc(string fileName)). As of now, not all attributes are honored in the save (only bold, fontsize, italic and underline).
93 |
94 | **Added 2025/02/27**
95 | ver 1.2.0 - Copying of richtext content (rtf format) is now possible. Some navigation and pasting fixes/improvements. Also pasting of large-volume text is now much faster. Technically 1.0.16 should have been numbered 1.2.0 but hey.
96 |
97 | **Added 2025/02/28**
98 | ver. 1.2.1 - FontFamily now included in rtf copy/paste, fixed Word reading error due to fonts
99 |
100 | **Added 2025/04/05**
101 | ver. 1.2.6 - some minor fixes: run break errors, and better handling of Word colors. Also setting ShowDebuggerPanelInDebugMode at runtime will now dynamically show/hide the Debugger panel.
102 |
103 | **Added 2025/04/06**
104 | ver. 1.3.0 - Can save/load as Html. Rtf images/line spacing now saved. Paragraph borders, colors and backgrounds supported.
105 |
106 | **Added 2025/04/09**
107 | ver. 1.3.2 - Changed the underlying strategy for Undo. Undo now creates clones instead of retaining objects, which was causing problems during complex Undo sequences.
108 | Also made ShowDebuggerPanelInDebugMode default to False.
109 |
--------------------------------------------------------------------------------
/AvRichTextBox/RequestExtensions.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using System;
4 | using System.Diagnostics;
5 |
6 | namespace AvRichTextBox;
7 |
8 | public static class RequestExtensions
9 | {
10 | //public static readonly AttachedProperty FlowDocumentProperty = AvaloniaProperty.RegisterAttached("FlowDocument", typeof(RequestExtensions));
11 | //public static void SetFlowDocument(AvaloniaObject element, FlowDocument value) => element.SetValue(FlowDocumentProperty, value);
12 | //public static FlowDocument GetFlowDocument(AvaloniaObject element) => (FlowDocument)element.GetValue(FlowDocumentProperty);
13 |
14 |
15 | public static readonly AttachedProperty TextBoxFocusRequestedProperty = AvaloniaProperty.RegisterAttached("TextBoxFocusRequested", typeof(RequestExtensions));
16 | public static void SetTextBoxFocusRequested(AvaloniaObject element, bool value) => element.SetValue(TextBoxFocusRequestedProperty, value);
17 | public static bool GetTextBoxFocusRequested(AvaloniaObject element) => (bool)element.GetValue(TextBoxFocusRequestedProperty);
18 |
19 | public static readonly AttachedProperty IsInlineUpdateRequestedProperty = AvaloniaProperty.RegisterAttached("IsInlineUpdateRequested", typeof(RequestExtensions));
20 | public static void SetIsInlineUpdateRequested(AvaloniaObject element, bool value) => element.SetValue(IsInlineUpdateRequestedProperty, value);
21 | public static bool GetIsInlineUpdateRequested(AvaloniaObject element) => (bool)element.GetValue(IsInlineUpdateRequestedProperty);
22 |
23 | public static readonly AttachedProperty InvalidateVisualRequestedProperty = AvaloniaProperty.RegisterAttached("InvalidateVisualRequested", typeof(RequestExtensions));
24 | public static void SetInvalidateVisualRequested(AvaloniaObject element, bool value) => element.SetValue(InvalidateVisualRequestedProperty, value);
25 | public static bool GetInvalidateVisualRequested(AvaloniaObject element) => (bool)element.GetValue(InvalidateVisualRequestedProperty);
26 |
27 | static RequestExtensions()
28 | {
29 | TextBoxFocusRequestedProperty.Changed.Subscribe(args =>
30 | {
31 | if (args.Sender is EditableParagraph edPar && (bool)args.NewValue.Value)
32 | {
33 | edPar.Focus();
34 | edPar.SetValue(TextBoxFocusRequestedProperty, false);
35 | }
36 | });
37 |
38 | IsInlineUpdateRequestedProperty.Changed.Subscribe(args =>
39 | {
40 | if (args.Sender is EditableParagraph edPar && (bool)args.NewValue.Value)
41 | {
42 | edPar.UpdateInlines();
43 | edPar.SetValue(IsInlineUpdateRequestedProperty, false);
44 | }
45 | });
46 |
47 | InvalidateVisualRequestedProperty.Changed.Subscribe(args =>
48 | {
49 | if (args.Sender is EditableParagraph edPar && (bool)args.NewValue.Value)
50 | {
51 | edPar.UpdateLayout();
52 | edPar.InvalidateVisual();
53 | edPar.SetValue(InvalidateVisualRequestedProperty, false);
54 | }
55 | });
56 |
57 |
58 |
59 |
60 |
61 | }
62 | }
63 |
64 |
65 |
--------------------------------------------------------------------------------
/AvRichTextBox/RichTextBox/RichTextBox.axaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
80 |
81 |
82 |
83 |
84 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
104 |
105 |
106 |
107 |
129 |
130 |
131 |
132 |
133 |
134 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/AvRichTextBox/RichTextBox/RichTextBoxInputClient.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using System;
4 | using System.Diagnostics;
5 | using Avalonia.Input.TextInput;
6 | using Avalonia.Interactivity;
7 | using Avalonia.Media;
8 | using Avalonia.Platform;
9 |
10 | namespace AvRichTextBox;
11 |
12 | public partial class RichTextBox
13 | {
14 |
15 | public class RichTextBoxTextInputClient : TextInputMethodClient
16 | {
17 | private readonly RichTextBox _owner;
18 |
19 | public RichTextBoxTextInputClient(RichTextBox owner)
20 | {
21 | _owner = owner;
22 | //this.SelectionChanged += RichTextBoxTextInputClient_SelectionChanged;
23 | //this.TextViewVisualChanged += RichTextBoxTextInputClient_TextViewVisualChanged;
24 | //this.ShowInputPanel();
25 |
26 | }
27 |
28 | private void RichTextBoxTextInputClient_TextViewVisualChanged(object? sender, EventArgs e)
29 | {
30 | //Debug.WriteLine("visual changed");
31 | }
32 |
33 | private void RichTextBoxTextInputClient_SelectionChanged(object? sender, EventArgs e)
34 | {
35 | //Debug.WriteLine("selection changed");
36 | }
37 |
38 | private double GetAdjustedCursorY (double yval)
39 | {
40 | //Debug.WriteLine("ownerbounds bottom=" + _owner.Bounds.Bottom + " // yval= " + yval);
41 | return (yval > _owner.Bounds.Bottom - 200) ? yval - 200 : yval + 22;
42 | }
43 |
44 | public override Rect CursorRectangle => new(_owner.CursorPosition.X + 12, GetAdjustedCursorY(_owner.CursorPosition.Y), 1, 0);
45 |
46 | public void UpdateCursorPosition()
47 | {
48 | RaiseCursorRectangleChanged();
49 | }
50 |
51 | public override bool SupportsPreedit => true;
52 |
53 | public override bool SupportsSurroundingText => false;
54 | public override string SurroundingText => "";
55 |
56 |
57 | public override TextSelection Selection
58 | {
59 | get => new (_owner.FlowDoc.Selection.Start, _owner.FlowDoc.Selection.End);
60 | set { }
61 | }
62 |
63 | public override Visual TextViewVisual => null!;
64 |
65 | public override void SetPreeditText(string? preeditText)
66 | {
67 | _owner.InsertPreeditText(preeditText!);
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/AvRichTextBox/RichTextBox/RichTextBox_DocEditing.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Documents;
3 | using Avalonia.Media;
4 | using System.Diagnostics;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace AvRichTextBox;
8 |
9 | public partial class RichTextBox
10 | {
11 |
12 | private void RichTextBox_TextInput(object? sender, Avalonia.Input.TextInputEventArgs e)
13 | {
14 | FlowDoc.InsertText(e.Text);
15 | UpdateCurrentParagraphLayout();
16 |
17 | if (PreeditOverlay.IsVisible)
18 | HideIMEOverlay();
19 |
20 | }
21 |
22 | private void HideIMEOverlay()
23 | {
24 | _preeditText = "";
25 | PreeditOverlay.IsVisible = false;
26 |
27 | }
28 |
29 | internal void UpdateCurrentParagraphLayout()
30 | {
31 | this.UpdateLayout();
32 | rtbVM.UpdateCursorVisible();
33 | }
34 |
35 | internal void InsertParagraph()
36 | {
37 | FlowDoc.InsertParagraph(true, FlowDoc.Selection.Start);
38 | UpdateCurrentParagraphLayout();
39 |
40 | }
41 |
42 | internal void InsertLineBreak()
43 | {
44 | FlowDoc.InsertLineBreak();
45 | UpdateCurrentParagraphLayout();
46 |
47 | }
48 |
49 | public void SearchText(string searchText)
50 | {
51 | MatchCollection matches = Regex.Matches(FlowDoc.Text, searchText);
52 |
53 | if (matches.Count > 0)
54 | FlowDoc.Select(matches[0].Index, matches[0].Length);
55 |
56 |
57 | foreach (Match m in matches)
58 | {
59 | TextRange trange = new (FlowDoc, m.Index, m.Index + m.Length);
60 | FlowDoc.ApplyFormattingRange(Inline.FontStretchProperty, FontStretch.UltraCondensed, trange);
61 | FlowDoc.ApplyFormattingRange(Inline.ForegroundProperty, new SolidColorBrush(Colors.BlueViolet), trange);
62 | FlowDoc.ApplyFormattingRange(Inline.BackgroundProperty, new SolidColorBrush(Colors.Wheat), trange);
63 | }
64 |
65 |
66 |
67 | }
68 |
69 |
70 | private void PerformDelete(bool backspace)
71 | {
72 |
73 | if (FlowDoc.Selection!.Length > 0)
74 | FlowDoc.DeleteSelection();
75 | else
76 | {
77 | if (backspace)
78 | if (FlowDoc.Selection.Start == 0) return;
79 | else
80 | if (FlowDoc.Selection.Start >= FlowDoc.Selection.StartParagraph.StartInDoc + FlowDoc.Selection.StartParagraph.BlockLength)
81 | return;
82 |
83 | FlowDoc.DeleteChar(backspace);
84 | }
85 |
86 | UpdateCurrentParagraphLayout();
87 | }
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/AvRichTextBox/RichTextBox/RichTextBox_KeyDown.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Input;
2 | using System.Diagnostics;
3 |
4 |
5 | namespace AvRichTextBox;
6 |
7 | public partial class RichTextBox
8 | {
9 |
10 | private void RichTextBox_KeyDown(object? sender, KeyEventArgs e)
11 | {
12 |
13 | if (e.KeyModifiers.HasFlag(KeyModifiers.Control))
14 | {
15 | e.Handled = true;
16 |
17 | switch (e.Key)
18 | {
19 | case Key.I:
20 | ToggleItalics();
21 | break;
22 |
23 | case Key.B:
24 | ToggleBold();
25 | break;
26 |
27 | case Key.U:
28 | ToggleUnderlining();
29 | break;
30 |
31 | case Key.C:
32 | CopyToClipboard();
33 | break;
34 |
35 | case Key.V:
36 | PasteFromClipboard();
37 | break;
38 |
39 | case Key.Home: // Ctrl-Home
40 | FlowDoc.MoveToDocStart();
41 | FlowDocSV.ScrollToHome();
42 | break;
43 |
44 | case Key.End: // Ctrl-End
45 | FlowDoc.MoveToDocEnd();
46 | break;
47 |
48 | case Key.Z:
49 | FlowDoc.Undo();
50 | break;
51 |
52 | case Key.A:
53 | FlowDoc.SelectAll();
54 | break;
55 |
56 | case Key.Delete:
57 | FlowDoc.DeleteWord(false);
58 | break;
59 |
60 | case Key.Back:
61 | FlowDoc.DeleteWord(true);
62 | break;
63 |
64 | case Key.Right:
65 | FlowDoc.MoveRightWord();
66 | break;
67 |
68 | case Key.Left:
69 | FlowDoc.MoveLeftWord();
70 | break;
71 | }
72 | }
73 | else
74 | {
75 |
76 | switch (e.Key)
77 | {
78 | case Key.Escape:
79 |
80 | if (PreeditOverlay.IsVisible)
81 | HideIMEOverlay();
82 |
83 | break;
84 |
85 | case Key.Enter:
86 | if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
87 | InsertLineBreak();
88 | else
89 | InsertParagraph();
90 | break;
91 | case Key.Home:
92 | FlowDoc.MoveToStartOfLine(e.KeyModifiers.HasFlag(KeyModifiers.Shift));
93 | break;
94 |
95 | case Key.End:
96 | FlowDoc.MoveToEndOfLine(e.KeyModifiers.HasFlag(KeyModifiers.Shift));
97 | break;
98 |
99 | case Key.Right:
100 | if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
101 | FlowDoc.ExtendSelectionRight();
102 | else
103 | FlowDoc.MoveSelectionRight(false);
104 | FlowDoc.ResetInsertFormatting();
105 | break;
106 |
107 | case Key.Left:
108 | if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
109 | FlowDoc.ExtendSelectionLeft();
110 | else
111 | FlowDoc.MoveSelectionLeft(false);
112 | FlowDoc.ResetInsertFormatting();
113 | break;
114 |
115 | case Key.Up:
116 | if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
117 | FlowDoc.ExtendSelectionUp();
118 | else
119 | FlowDoc.MoveSelectionUp(false);
120 | break;
121 |
122 | case Key.Down:
123 | if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
124 | FlowDoc.ExtendSelectionDown();
125 | else
126 | FlowDoc.MoveSelectionDown(true);
127 | break;
128 |
129 | case Key.Back:
130 | PerformDelete(true);
131 | break;
132 |
133 | case Key.Delete:
134 | PerformDelete(false);
135 | break;
136 |
137 | case Key.PageDown:
138 | MovePage(1, e.KeyModifiers.HasFlag(KeyModifiers.Shift));
139 | break;
140 |
141 | case Key.PageUp:
142 |
143 | MovePage(-1, e.KeyModifiers.HasFlag(KeyModifiers.Shift));
144 |
145 | break;
146 |
147 | }
148 |
149 | rtbVM.CursorVisible = (rtbVM.FlowDoc.Selection.Length == 0);
150 | if (client != null)
151 | UpdatePreeditOverlay();
152 |
153 | }
154 |
155 |
156 | }
157 |
158 |
159 |
160 | }
161 |
162 |
163 |
--------------------------------------------------------------------------------
/AvRichTextBox/RichTextBox/RichTextBox_PointerEvents.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Data.Core;
4 | using Avalonia.Input;
5 | using Avalonia.Media;
6 | using Avalonia.VisualTree;
7 | using DynamicData;
8 | using System.Collections.Generic;
9 | using System.Diagnostics;
10 | using System.Linq;
11 |
12 | namespace AvRichTextBox;
13 |
14 | public partial class RichTextBox
15 | {
16 |
17 | EditableParagraph? currentMouseOverEP = null;
18 |
19 | internal void EditableParagraph_MouseMove(EditableParagraph edPar, int charIndex)
20 | {
21 | if (!PointerDownOverRTB)
22 | currentMouseOverEP = edPar;
23 |
24 | }
25 |
26 |
27 | private void EditableParagraph_LostFocus(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
28 | {
29 | this.Focus();
30 | }
31 |
32 |
33 | internal int SelectionOrigin = 0;
34 | bool PointerDownOverRTB = false;
35 |
36 | private void FlowDocSV_PointerPressed(object? sender, PointerPressedEventArgs e)
37 | {
38 | if (currentMouseOverEP == null) return;
39 |
40 | PointerDownOverRTB = true;
41 |
42 | TextHitTestResult hitCarIndex = currentMouseOverEP.TextLayout.HitTestPoint(e.GetPosition(currentMouseOverEP));
43 | Paragraph thisPar = (Paragraph)currentMouseOverEP.DataContext!;
44 | if (thisPar == null) return;
45 | SelectionOrigin = thisPar.StartInDoc + hitCarIndex.TextPosition;
46 |
47 | //Clear all selections in all paragraphs
48 | foreach (Paragraph p in FlowDoc.Blocks.Where(pp => pp.SelectionLength != 0)) { p.ClearSelection(); }
49 |
50 | FlowDoc.Selection.Start = SelectionOrigin;
51 | FlowDoc.Selection.CollapseToStart();
52 |
53 | //e.Pointer.Capture(null);
54 | //e.Pointer.Capture(this);
55 |
56 | }
57 |
58 | private void FlowDocSV_PointerMoved(object? sender, PointerEventArgs e)
59 | {
60 |
61 | if (PointerDownOverRTB)
62 | {
63 | EditableParagraph overEP = null!;
64 |
65 | double RTBTransformedY = this.GetTransformedBounds()!.Value.Clip.Y;
66 |
67 | foreach (KeyValuePair kvp in VisualHelper.GetVisibleEditableParagraphs(FlowDocSV))
68 | { //Debug.WriteLine("visiPar = " + kvp.Key.Text);
69 |
70 | Point ePoint = e.GetCurrentPoint(FlowDocSV).Position;
71 | Rect thisEPRect = new(kvp.Value.X - DocIC.Margin.Left, kvp.Value.Y, kvp.Value.Width, kvp.Value.Height);
72 |
73 | double adjustedMouseY = ePoint.Y + RTBTransformedY;
74 | bool epContainsPoint = thisEPRect.Top <= adjustedMouseY && thisEPRect.Bottom >= adjustedMouseY;
75 |
76 | if (epContainsPoint)
77 | { overEP = kvp.Key; break; }
78 | }
79 |
80 | if (overEP != null)
81 | {
82 | TextHitTestResult hitCharIndex = overEP.TextLayout.HitTestPoint(e.GetPosition(overEP));
83 | int charIndex = hitCharIndex.TextPosition;
84 |
85 | Paragraph thisPar = (Paragraph)overEP.DataContext!;
86 |
87 | if (thisPar.StartInDoc + charIndex < SelectionOrigin)
88 | { //Debug.WriteLine("startindoc = " + thisPar.StartInDoc + " :::charindex = " + charIndex + " :::selectionorigin= " + SelectionOrigin);
89 | FlowDoc.SelectionExtendMode = FlowDocument.ExtendMode.ExtendModeLeft;
90 | FlowDoc.Selection.End = SelectionOrigin;
91 | FlowDoc.Selection.Start = thisPar.StartInDoc + charIndex;
92 | }
93 | else
94 | {
95 | FlowDoc.SelectionExtendMode = FlowDocument.ExtendMode.ExtendModeRight;
96 | FlowDoc.Selection.Start = SelectionOrigin;
97 | FlowDoc.Selection.End = thisPar.StartInDoc + charIndex;
98 | }
99 |
100 | FlowDoc.EnsureSelectionContinuity();
101 | }
102 | }
103 |
104 | }
105 |
106 | private void RichTextBox_PointerReleased(object? sender, PointerReleasedEventArgs e)
107 | {
108 | PointerDownOverRTB = false;
109 |
110 | }
111 |
112 | private void FlowDocSV_PointerReleased(object? sender, PointerReleasedEventArgs e)
113 | {
114 | //e.Pointer.Capture(null);
115 | PointerDownOverRTB = false;
116 |
117 | }
118 |
119 | private void RichTextBox_PointerExited(object? sender, PointerEventArgs e)
120 | {
121 | //PointerDownOverRTB = false;
122 |
123 | }
124 |
125 | }
126 |
127 |
128 |
--------------------------------------------------------------------------------
/AvRichTextBox/RichTextBox/RichTextBox_RunFormatting.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Input;
3 | using DocumentFormat.OpenXml.VariantTypes;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Diagnostics;
7 | using RtfDomParserAv;
8 | using System.Linq;
9 | using System.Threading.Tasks;
10 | using static AvRichTextBox.FlowDocument;
11 |
12 | namespace AvRichTextBox;
13 |
14 | public partial class RichTextBox
15 | {
16 |
17 | private void ToggleItalics()
18 | {
19 | FlowDoc.ToggleItalic();
20 |
21 | }
22 |
23 | private void ToggleBold()
24 | {
25 | FlowDoc.ToggleBold();
26 |
27 | }
28 |
29 | private void ToggleUnderlining()
30 | {
31 | FlowDoc.ToggleUnderlining();
32 |
33 | }
34 |
35 | private void CopyToClipboard()
36 | {
37 |
38 | var dataObject = new DataObject();
39 |
40 | //create rtf string
41 | List newInlines = FlowDoc.GetRangeInlines(FlowDoc.Selection);
42 | string rtfString = RtfConversions.GetRtfFromInlines(newInlines);
43 | byte[] rtfbytes = System.Text.Encoding.Default.GetBytes(rtfString);
44 |
45 | dataObject.Set("Rich Text Format", rtfbytes);
46 | dataObject.Set("Text", FlowDoc.Selection.GetText());
47 |
48 | TopLevel.GetTopLevel(this)!.Clipboard!.SetDataObjectAsync(dataObject);
49 |
50 | }
51 |
52 |
53 | private async void PasteFromClipboard()
54 | {
55 | bool TextPasted = false;
56 | int originalSelectionStart = FlowDoc.Selection.Start;
57 | int newSelPoint = originalSelectionStart;
58 |
59 | string[] formats = await TopLevel.GetTopLevel(this)!.Clipboard!.GetFormatsAsync();
60 | if (formats.Contains ("Rich Text Format"))
61 | {
62 | object? rtfobj = await TopLevel.GetTopLevel(this)!.Clipboard!.GetDataAsync("Rich Text Format");
63 | if (rtfobj != null)
64 | {
65 | byte[] rtfbytes = (byte[])rtfobj;
66 | string rtfstring = System.Text.Encoding.Default.GetString(rtfbytes!);
67 |
68 | RTFDomDocument dom = new();
69 | dom.LoadRTFText(rtfstring);
70 | List insertInlines = RtfConversions.GetInlinesFromRtf(dom);
71 | insertInlines.Reverse();
72 | int addedchars = FlowDoc.SetRangeToInlines(FlowDoc.Selection, insertInlines);
73 |
74 | newSelPoint = Math.Min(newSelPoint + addedchars, FlowDoc.DocEndPoint - 1);
75 |
76 | TextPasted = true;
77 | }
78 | }
79 | else if (formats.Contains("Text"))
80 | {
81 | object? textobj = await TopLevel.GetTopLevel(this)!.Clipboard!.GetDataAsync("Text");
82 |
83 | if (textobj != null)
84 | {
85 | string pasteText = textobj.ToString()!;
86 | FlowDoc.SetRangeToText(FlowDoc.Selection, pasteText);
87 |
88 | newSelPoint = Math.Min(newSelPoint + pasteText.Length, FlowDoc.DocEndPoint - 1);
89 |
90 | TextPasted = true;
91 | }
92 | }
93 |
94 | if (TextPasted)
95 | {
96 | this.DocIC.UpdateLayout();
97 | await Task.Delay(100); //necessary for following operations
98 |
99 | FlowDoc.Selection.EndParagraph.CallRequestInlinesUpdate(); // important
100 | FlowDoc.Selection.EndParagraph.UpdateEditableRunPositions();
101 |
102 | FlowDoc.Select(newSelPoint, 0);
103 | FlowDoc.UpdateSelection();
104 |
105 | FlowDoc.Selection.BiasForwardStart = false;
106 | FlowDoc.Selection.BiasForwardEnd = false;
107 | FlowDoc.SelectionExtendMode = ExtendMode.ExtendModeNone;
108 |
109 | CreateClient();
110 |
111 |
112 | }
113 |
114 | }
115 |
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/AvRichTextBox/RichTextBox/RichTextBox_SelectionRect.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Media;
4 | using Avalonia.Media.TextFormatting;
5 | using System;
6 | using System.Diagnostics;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 |
10 | namespace AvRichTextBox;
11 |
12 | public partial class RichTextBox : UserControl
13 | {
14 |
15 | internal void SelectionStart_RectChanged(EditableParagraph edPar)
16 | {
17 | edPar.UpdateLayout();
18 |
19 | TextLayout tlayout = edPar.TextLayout;
20 |
21 | Rect selStartRect = tlayout.HitTestTextPosition(edPar.SelectionStart);
22 | Rect prevCharRect = tlayout.HitTestTextPosition(edPar.SelectionStart - 1);
23 |
24 | Point? selStartPoint = edPar.TranslatePoint(selStartRect.Position, DocIC);
25 | Point? prevCharPoint = edPar.TranslatePoint(prevCharRect.Position, DocIC);
26 |
27 | if (selStartPoint == null || prevCharPoint == null) return;
28 |
29 | //Debug.WriteLine("selstartpoint= " + selStartPoint!.Value.Y);
30 |
31 | if (selStartPoint != null)
32 | FlowDoc.Selection.StartRect = new Rect((Point)selStartPoint!, selStartRect.Size);
33 |
34 | if (prevCharPoint != null)
35 | FlowDoc.Selection.PrevCharRect = new Rect((Point)prevCharPoint!, prevCharRect.Size);
36 |
37 | Paragraph thisPar = (Paragraph)edPar.DataContext!;
38 | //if (thisPar == null) return;
39 |
40 | thisPar.DistanceSelectionStartFromLeft = selStartRect.Left;
41 |
42 | int lineNo = tlayout.GetLineIndexFromCharacterIndex(edPar.SelectionStart, false);
43 | thisPar.IsStartAtFirstLine = lineNo == 0;
44 | thisPar.IsStartAtLastLine = (lineNo == tlayout.TextLines.Count - 1);
45 |
46 | if (thisPar.IsStartAtFirstLine)
47 | thisPar.CharPrevLineStart = edPar.SelectionStart;
48 | else
49 | { // get index of first char on previous line
50 | thisPar.CharPrevLineStart = GetClosestIndex(edPar, lineNo, thisPar.DistanceSelectionStartFromLeft, -1);
51 | //IEditable? nextInline = FlowDoc.GetNextInline(FlowDoc.Selection.GetStartInline());
52 | //if (nextInline != null && nextInline.IsLineBreak)
53 | //{
54 | // Debug.WriteLine("thisinlinetext=" + FlowDoc.Selection.GetStartInline().InlineText);
55 | // Debug.WriteLine("nextinlinetext=" + nextInline.InlineText);
56 | //}
57 | // //thisPar.CharPrevLineStart += 1;
58 | }
59 |
60 |
61 | if (thisPar.IsStartAtLastLine)
62 | thisPar.CharNextLineStart = edPar.SelectionEnd - tlayout.TextLines[lineNo].FirstTextSourceIndex;
63 | else
64 | thisPar.CharNextLineStart = GetClosestIndex(edPar, lineNo, thisPar.DistanceSelectionStartFromLeft, 1);
65 |
66 | thisPar.FirstIndexStartLine = tlayout.TextLines[lineNo].FirstTextSourceIndex;
67 | thisPar.FirstIndexLastLine = tlayout.TextLines[^1].FirstTextSourceIndex;
68 |
69 |
70 | //**********Fix cursor height and position*********:
71 | int lineIndex = tlayout.GetLineIndexFromCharacterIndex(edPar.SelectionStart, false);
72 | rtbVM.CursorHeight = tlayout.TextLines[lineIndex].Extent;
73 | if (rtbVM.CursorHeight == 0)
74 | rtbVM.CursorHeight = tlayout.TextLines[lineIndex].Height;
75 | rtbVM.CursorHeight += 5; // give it an extra bit
76 |
77 |
78 | double cursorML = selStartPoint!.Value.X;
79 | double cursorMT = tlayout.TextLines[lineIndex].Start;
80 |
81 | if (FlowDoc.Selection.IsAtEndOfLineSpace)
82 | {
83 | cursorML = FlowDoc.Selection!.PrevCharRect!.Right;
84 | cursorMT = FlowDoc.Selection!.PrevCharRect.Top + 1;
85 | }
86 | else
87 | cursorMT = selStartPoint.Value.Y;
88 |
89 | rtbVM.CursorMargin = new Thickness(cursorML, cursorMT, 0, 0);
90 | rtbVM.UpdateCursorVisible();
91 |
92 | }
93 |
94 | internal void SelectionEnd_RectChanged(EditableParagraph edPar)
95 | {
96 | edPar.UpdateLayout();
97 |
98 | Rect selEndRect = edPar.TextLayout.HitTestTextPosition(edPar.SelectionEnd);
99 |
100 | Point? selEndPoint = edPar.TranslatePoint(selEndRect.Position, DocIC);
101 | if (selEndPoint != null)
102 | FlowDoc.Selection.EndRect = new Rect((Point)selEndPoint!, selEndRect.Size);
103 |
104 | Paragraph thisPar = (Paragraph)edPar.DataContext!;
105 | //if (thisPar == null) return;
106 |
107 | thisPar.DistanceSelectionEndFromLeft = edPar.TextLayout.HitTestTextPosition(edPar.SelectionEnd).Left;
108 | int lineNo = edPar.TextLayout.GetLineIndexFromCharacterIndex(edPar.SelectionEnd, false);
109 | thisPar.IsEndAtLastLine = lineNo == edPar.TextLayout.TextLines.Count - 1;
110 |
111 | thisPar.IsEndAtFirstLine = (lineNo == 0);
112 | if (thisPar.IsEndAtLastLine)
113 | {
114 | thisPar.LastIndexEndLine = thisPar.BlockLength;
115 | thisPar.CharNextLineEnd = edPar.Text!.Length + 1 + edPar.SelectionEnd - edPar.TextLayout.TextLines[lineNo].FirstTextSourceIndex;
116 | }
117 | else
118 | {
119 | //Debug.WriteLine("partext = " + thisPar.Text);
120 | //Debug.WriteLine("partext = " + thisPar.Inlines.Count);
121 |
122 | //IEditable ied = FlowDoc.Selection.GetStartInline();
123 | //Debug.WriteLine("ied.= " + ied.DisplayInlineText);
124 |
125 | //Debug.WriteLine("count = " + FlowDoc.GetRangeInlines(FlowDoc.Selection).Count);
126 | //if (FlowDoc.GetRangeInlines(FlowDoc.Selection).Count > 0)
127 | // Debug.WriteLine("count = *" + FlowDoc.GetRangeInlines(FlowDoc.Selection)[0].InlineText + "*");
128 |
129 | TextLine tline = edPar.TextLayout.TextLines[lineNo];
130 | //Debug.WriteLine("textstring =" + string.Join("\r", (tline.TextRuns.Select(tr=>tr.Text))));
131 | //Debug.WriteLine("lastis rn? " + (tline.TextRuns.Last().Text.ToString() == "\r\n"));
132 |
133 | int goBackNo = 1;
134 | if (tline.TextRuns.Count > 0)
135 | {
136 | if (tline.TextRuns[tline.TextRuns.Count - 1].Text.ToString() == "\r\n")
137 | goBackNo++;
138 |
139 | }
140 |
141 | thisPar.LastIndexEndLine = edPar.TextLayout.TextLines[lineNo + 1].FirstTextSourceIndex - goBackNo;
142 | thisPar.CharNextLineEnd = GetClosestIndex(edPar, lineNo, thisPar.DistanceSelectionEndFromLeft, 1);
143 | }
144 |
145 | if (!thisPar.IsEndAtFirstLine)
146 | thisPar.CharPrevLineEnd = GetClosestIndex(edPar, lineNo, thisPar.DistanceSelectionEndFromLeft, -1);
147 |
148 |
149 | thisPar.FirstIndexLastLine = edPar.TextLayout.TextLines[^1].FirstTextSourceIndex;
150 |
151 | rtbVM.UpdateCursorVisible();
152 |
153 | }
154 |
155 |
156 | private static int GetClosestIndex(EditableParagraph edPar, int lineNo, double distanceFromLeft, int direction)
157 | {
158 | CharacterHit chit = edPar.TextLayout.TextLines[lineNo + direction].GetCharacterHitFromDistance(distanceFromLeft);
159 |
160 | double CharDistanceDiffThis = Math.Abs(distanceFromLeft - edPar.TextLayout.HitTestTextPosition(chit.FirstCharacterIndex).Left);
161 | double CharDistanceDiffNext = Math.Abs(distanceFromLeft - edPar.TextLayout.HitTestTextPosition(chit.FirstCharacterIndex + 1).Left);
162 |
163 | if (CharDistanceDiffThis > CharDistanceDiffNext)
164 | return chit.FirstCharacterIndex + 1;
165 | else
166 | return chit.FirstCharacterIndex;
167 |
168 |
169 | }
170 |
171 | private void EditableParagraph_CharIndexRect_Notified(EditableParagraph edPar, Rect selStartRect)
172 | {
173 | //Point? selStartPoint = edPar.TranslatePoint(selStartRect.Position, DocIC);
174 | //if (selStartPoint != null)
175 | // FlowDoc.Selection.StartRect = new Rect((Point)selStartPoint!, selStartRect.Size);
176 |
177 | }
178 |
179 |
180 | }
181 |
182 |
183 |
--------------------------------------------------------------------------------
/AvRichTextBox/RichTextBoxViewModel/RichTextBoxViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Threading;
3 | using System;
4 | using System.ComponentModel;
5 | using System.Diagnostics;
6 | using System.Runtime.CompilerServices;
7 | using static AvRichTextBox.FlowDocument;
8 |
9 | namespace AvRichTextBox;
10 |
11 | public class RichTextBoxViewModel : INotifyPropertyChanged
12 | {
13 | public event PropertyChangedEventHandler? PropertyChanged;
14 | private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
15 |
16 | public delegate void FlowDocChanged_Handler();
17 | internal event FlowDocChanged_Handler? FlowDocChanged;
18 |
19 | private Vector _RTBScrollOffset = new (0, 0);
20 | public Vector RTBScrollOffset { get => _RTBScrollOffset; set { if (_RTBScrollOffset != value) _RTBScrollOffset = value; NotifyPropertyChanged(nameof(RTBScrollOffset)); } }
21 |
22 | public double MinWidth => RunDebuggerVisible ? 500 : 100;
23 |
24 | private FlowDocument _FlowDoc = null!;
25 | //public FlowDocument FlowDoc { get => _FlowDoc; set { _FlowDoc = value; NotifyPropertyChanged(nameof(FlowDoc)); } }
26 | public FlowDocument FlowDoc { get => _FlowDoc; set { _FlowDoc = value; NotifyPropertyChanged(nameof(FlowDoc)); FlowDocChanged?.Invoke(); } }
27 |
28 | private bool _RunDebuggerVisible = false;
29 | public bool RunDebuggerVisible { get => _RunDebuggerVisible; set { _RunDebuggerVisible = value; NotifyPropertyChanged(nameof(RunDebuggerVisible)); } }
30 |
31 | public RichTextBoxViewModel()
32 | {
33 | //FlowDoc.ScrollInDirection += FlowDoc_ScrollInDirection;
34 | //FlowDoc.UpdateRTBCursor += FlowDoc_UpdateRTBCursor;
35 | }
36 |
37 | internal void FlowDoc_UpdateRTBCursor()
38 | {
39 | UpdateCursorVisible();
40 | }
41 |
42 | internal double ScrollViewerHeight = 10;
43 |
44 | private double _CursorHeight = 5;
45 | public double CursorHeight { get => _CursorHeight; set { _CursorHeight = value; NotifyPropertyChanged(nameof(CursorHeight)); } }
46 |
47 | private Thickness _CursorMargin = new (0);
48 | public Thickness CursorMargin { get => _CursorMargin; set { _CursorMargin = value; NotifyPropertyChanged(nameof(CursorMargin)); } }
49 |
50 | private bool _CursorVisible = true;
51 | public bool CursorVisible { get => _CursorVisible; set { _CursorVisible = value; NotifyPropertyChanged(nameof(CursorVisible)); } }
52 |
53 | internal void UpdateCursorVisible()
54 | {
55 |
56 | FlowDoc.Selection.StartParagraph?.CallRequestInvalidateVisual();
57 |
58 | CursorVisible = FlowDoc.Selection.Length == 0;
59 |
60 | }
61 |
62 |
63 | internal void FlowDoc_ScrollInDirection(int direction)
64 | {
65 |
66 | double scrollPadding = 30;
67 | if (direction == 1)
68 | {
69 | double checkPointY = FlowDoc.Selection!.EndRect!.Y;
70 |
71 | if (FlowDoc.SelectionExtendMode == ExtendMode.ExtendModeLeft)
72 | checkPointY = FlowDoc.Selection!.StartRect!.Y;
73 |
74 | if (checkPointY > RTBScrollOffset.Y + ScrollViewerHeight - scrollPadding)
75 | RTBScrollOffset = RTBScrollOffset.WithY(checkPointY - ScrollViewerHeight + scrollPadding);
76 | //RTBScrollOffset = RTBScrollOffset.WithY(checkPointY + scrollPadding);
77 | }
78 | else
79 | {
80 | double checkPointY = FlowDoc.Selection!.StartRect!.Y;
81 | if (FlowDoc.SelectionExtendMode == ExtendMode.ExtendModeRight)
82 | checkPointY = FlowDoc.Selection!.EndRect!.Y;
83 |
84 |
85 | if (checkPointY < RTBScrollOffset.Y)
86 | RTBScrollOffset = RTBScrollOffset.WithY(checkPointY);
87 | }
88 |
89 | }
90 |
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/AvRichTextBox/RtfDomParserAv.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cuikp/AvRichTextBox/145e48b7aecf1b896dc4a5fd573061b5bcd449d9/AvRichTextBox/RtfDomParserAv.dll
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/HtmlConversions/ContentToHtml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Media;
2 | using Avalonia.Media.Imaging;
3 | using HtmlAgilityPack;
4 | using System.IO;
5 | using System;
6 | using System.Net;
7 | using System.Text;
8 | using Avalonia.Controls;
9 | using System.Linq;
10 |
11 | namespace AvRichTextBox;
12 |
13 | internal static partial class HtmlConversions
14 | {
15 | internal static HtmlDocument GetHtmlFromFlowDocument(FlowDocument fdoc)
16 | {
17 |
18 | HtmlDocument hdoc = new();
19 |
20 | HtmlNode html = hdoc.CreateElement("html");
21 | HtmlNode head = hdoc.CreateElement("head");
22 | HtmlNode body = hdoc.CreateElement("body");
23 |
24 | html.AppendChild(head);
25 | html.AppendChild(body);
26 | hdoc.DocumentNode.AppendChild(html);
27 |
28 |
29 | if (fdoc.PagePadding != default)
30 | {
31 | var pad = fdoc.PagePadding;
32 | var padStyle = $"padding:{pad.Top}px {pad.Right}px {pad.Bottom}px {pad.Left}px;";
33 | body.SetAttributeValue("style", padStyle);
34 | }
35 |
36 | foreach (Block b in fdoc.Blocks){
37 |
38 | if (b is Paragraph p)
39 | {
40 | HtmlNode parnode = hdoc.CreateElement("p");
41 | //parnode.SetAttributeValue("style", GetParStyle(p));
42 |
43 | foreach (IEditable ied in p.Inlines)
44 | {
45 | HtmlNode spanNode = hdoc.CreateElement("span");
46 |
47 | switch (ied)
48 | {
49 | case EditableRun erun:
50 | spanNode.InnerHtml = WebUtility.HtmlEncode(erun.Text ?? "");
51 | spanNode.SetAttributeValue("style", GetInlineStyle(erun));
52 | break;
53 |
54 | case EditableLineBreak elbreak:
55 | spanNode = hdoc.CreateElement("br");
56 | //parnode.AppendChild(br);
57 | break;
58 |
59 | case EditableInlineUIContainer eUIC:
60 |
61 | if (eUIC.Child is Image img && img.Source is Bitmap bmp)
62 | {
63 | using var memStream = new MemoryStream();
64 | bmp.Save(memStream);
65 | var base64 = Convert.ToBase64String(memStream.ToArray());
66 |
67 | var imgNode = hdoc.CreateElement("img");
68 | imgNode.SetAttributeValue("src", $"data:image/png;base64,{base64}");
69 | imgNode.SetAttributeValue("width", img.Width.ToString());
70 | imgNode.SetAttributeValue("height", img.Height.ToString());
71 |
72 | parnode.AppendChild(imgNode);
73 | }
74 | break;
75 |
76 | }
77 |
78 | parnode.AppendChild(spanNode);
79 |
80 | }
81 |
82 | parnode.SetAttributeValue("style", GetParStyle(p));
83 |
84 | body.AppendChild(parnode);
85 | }
86 | }
87 |
88 |
89 | return hdoc;
90 |
91 | }
92 |
93 | private static string GetParStyle(Paragraph p)
94 | {
95 | var parStyle = new StringBuilder();
96 |
97 | if (p.LineSpacing > 0)
98 | parStyle.Append($"line-height:{p.LineHeight}px;");
99 |
100 | switch (p.TextAlignment)
101 | {
102 | case TextAlignment.Center:
103 | parStyle.Append("text-align:center;");
104 | break;
105 | case TextAlignment.Right:
106 | parStyle.Append("text-align:right;");
107 | break;
108 | case TextAlignment.Left:
109 | parStyle.Append("text-align:left;");
110 | break;
111 | case TextAlignment.Justify:
112 | parStyle.Append("text-align:justify;");
113 | break;
114 | }
115 |
116 | if (p.Margin != default)
117 | parStyle.Append($"margin:{p.Margin.Top}px {p.Margin.Right}px {p.Margin.Bottom}px {p.Margin.Left}px;");
118 |
119 | string? parBackgroundColor = GetCssColor(p.Background);
120 | if (parBackgroundColor != null)
121 | parStyle.Append($"background-color:{parBackgroundColor};");
122 |
123 | var borderColor = GetCssColor(p.BorderBrush);
124 | if (borderColor != null)
125 | parStyle.Append($"border-color:{borderColor};");
126 |
127 | if (p.BorderThickness != default)
128 | parStyle.Append($"border-style:solid;border-width:{p.BorderThickness.Top}px {p.BorderThickness.Right}px {p.BorderThickness.Bottom}px {p.BorderThickness.Left}px;");
129 |
130 | return parStyle.ToString();
131 | }
132 |
133 |
134 | private static string GetInlineStyle(EditableRun run)
135 | {
136 | var sb = new StringBuilder();
137 |
138 | if (!string.IsNullOrEmpty(run.FontFamily.ToString()))
139 | sb.Append($"font-family:{run.FontFamily};");
140 |
141 | if (run.FontWeight == FontWeight.Bold)
142 | sb.Append("font-weight:bold;");
143 |
144 | if (run.FontStyle == FontStyle.Italic)
145 | sb.Append("font-style:italic;");
146 |
147 | if (run.FontSize > 0)
148 | sb.Append($"font-size:{run.FontSize}px;");
149 |
150 | string? foregroundColor = GetCssColor(run.Foreground);
151 | if (foregroundColor != null)
152 | sb.Append($"color:{foregroundColor};");
153 |
154 | string? backgroundColor = GetCssColor(run.Background);
155 | if (backgroundColor != null)
156 | sb.Append($"background-color:{backgroundColor};");
157 |
158 |
159 | if (run.TextDecorations != null)
160 | {
161 | foreach (var td in run.TextDecorations)
162 | {
163 | switch (td.Location)
164 | {
165 | case TextDecorationLocation.Underline:
166 | sb.Append("text-decoration:underline;");
167 | break;
168 | case TextDecorationLocation.Strikethrough:
169 | sb.Append("text-decoration:line-through;");
170 | break;
171 | }
172 | }
173 | }
174 |
175 | if (run.BaselineAlignment == BaselineAlignment.Superscript)
176 | sb.Append("vertical-align: super;");
177 | if (run.BaselineAlignment == BaselineAlignment.Subscript)
178 | sb.Append("vertical-align: sub;");
179 |
180 | return sb.ToString();
181 | }
182 |
183 |
184 | private static string? GetCssColor(IBrush? brush)
185 | {
186 | if (brush is SolidColorBrush solid)
187 | {
188 | var color = solid.Color;
189 | if (color == Colors.Transparent) return null;
190 | return $"#{color.R:X2}{color.G:X2}{color.B:X2}";
191 | //return $"rgba({c.R},{c.G},{c.B},{c.A / 255.0:F2})";
192 | }
193 |
194 | return null;
195 | }
196 |
197 |
198 |
199 | }
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/WordImportExport/CreateFlowDocument/GetFlowDocument.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Media;
3 | using DocumentFormat.OpenXml;
4 | using DocumentFormat.OpenXml.Packaging;
5 | using System;
6 | using System.Diagnostics;
7 | using System.Linq;
8 | using static AvRichTextBox.HelperMethods;
9 |
10 | namespace AvRichTextBox;
11 |
12 | internal static partial class WordConversions
13 | {
14 | internal static MainDocumentPart? mainDocPart;
15 | internal static string DefaultEastAsiaFont = "";
16 | internal static string DefaultAsciiFont = "";
17 |
18 | internal static void GetFlowDocument(MainDocumentPart mDocPart, FlowDocument fdoc)
19 | {
20 |
21 | try
22 | {
23 |
24 | mainDocPart = mDocPart;
25 |
26 | //Implement doc-wide properties someday:
27 |
28 | // Dim docVariablesList As List(Of DocumentVariables) = mainDocPart.DocumentSettingsPart.Settings.Descendants(Of DocumentVariables)().ToList()
29 | // 'foreach (DocumentVariables docVars in docVariablesList)
30 | // ' foreach (DocumentVariable docVar in docVars)
31 | //mainDocPart.DocumentSettingsPart.Settings
32 |
33 | //StyleDefinitionsPart? styles = mainDocPart.StyleDefinitionsPart!;
34 | //if (styles != null)
35 | //{
36 | // var defParProps = styles.Styles!.DocDefaults!.ParagraphPropertiesDefault;
37 |
38 | // //if (defParProps.ParagraphPropertiesBaseStyle != null)
39 | // //{
40 | // // if (defParProps.ParagraphPropertiesBaseStyle.SpacingBetweenLines == null)
41 | // // fdoc.LineHeight = 50;
42 | // // else
43 | // // fdoc.LineHeight = TwipToPix(Convert.ToDouble(defParProps.ParagraphPropertiesBaseStyle.SpacingBetweenLines.Line.Value));
44 | // //}
45 |
46 | // //var runFonts = styles.Styles.DocDefaults.RunPropertiesDefault.RunPropertiesBaseStyle.RunFonts;
47 |
48 | //if (runFonts != null)
49 | //{
50 | // DefaultAsciiFont = (runFonts.Ascii == null) ? "Times New Roman" : runFonts.Ascii.Value;
51 | // DefaultEastAsiaFont = (runFonts.EastAsia == null) ? "MS 明朝" : runFonts.EastAsia.Value;
52 | // if (DefaultEastAsiaFont == "Mincho") DefaultEastAsiaFont = "MS 明朝";
53 | // fdoc.FontFamily = new FontFamily(DefaultAsciiFont + ", " + DefaultEastAsiaFont);
54 | //}
55 |
56 | // //foreach (DocumentFormat.OpenXml.Wordprocessing.Style dStyle in styles.Styles.Descendants())
57 | // //{
58 | // // if (dStyle.StyleName.Val == "Normal")
59 | // // fdoc.FontSize = (dStyle.StyleRunProperties == null || dStyle.StyleRunProperties.FontSize == null) ? 18 : PointsToPixels(Convert.ToDouble(dStyle.StyleRunProperties.FontSize.Val)) / 2;
60 | // //}
61 | //}
62 |
63 | fdoc.PagePadding = new Thickness(50); //set a small default padding
64 |
65 | DocumentFormat.OpenXml.Wordprocessing.PageMargin? pMarg = mainDocPart.Document.Descendants().FirstOrDefault()!;
66 |
67 | if (pMarg != null)
68 | {
69 | double docmargT = TwipToPix(Convert.ToDouble((int)pMarg.Top!));
70 | double docmargR = TwipToPix(Convert.ToDouble((uint)pMarg.Right!));
71 | double docmargB = TwipToPix(Convert.ToDouble((int)pMarg.Bottom!));
72 | double docmargL = TwipToPix(Convert.ToDouble((uint)pMarg.Left!));
73 | fdoc.PagePadding = new Thickness(docmargL, docmargT, docmargR, docmargB);
74 | }
75 |
76 | //DocumentFormat.OpenXml.Wordprocessing.PageSize pSize = mainDocPart.Document.Descendants().FirstOrDefault();
77 | //if (pSize != null)
78 | //{
79 | // fdoc.PageWidth = TwipToPix(Convert.ToDouble((uint)pSize.Width));
80 | // fdoc.PageHeight = TwipToPix(Convert.ToDouble((uint)pSize.Height));
81 | //}
82 |
83 |
84 | OpenXmlElement? docBody = mainDocPart.Document.Body!;
85 |
86 | if (docBody != null)
87 | {
88 | foreach (OpenXmlElement section in docBody.Elements())
89 | { //MessageBox.Show(section.LocalName);
90 |
91 | switch (section.LocalName)
92 | {
93 | case "sectPr":
94 |
95 | //DocumentFormat.OpenXml.Wordprocessing.PageMargin? spMarg = section.Descendants().FirstOrDefault();
96 | if (pMarg != null)
97 | {
98 | double docmargT = TwipToPix(Convert.ToDouble((int)pMarg.Top!));
99 | double docmargR = TwipToPix(Convert.ToDouble((uint)pMarg.Right!));
100 | double docmargB = TwipToPix(Convert.ToDouble((int)pMarg.Bottom!));
101 | double docmargL = TwipToPix(Convert.ToDouble((uint)pMarg.Left!));
102 | fdoc.PagePadding = new Thickness(docmargL, docmargT, docmargR, docmargB);
103 | }
104 |
105 | //DocumentFormat.OpenXml.Wordprocessing.PageSize spSize = section.Descendants().FirstOrDefault();
106 | //if (pSize != null)
107 | //{
108 | // fdoc.PageWidth = TwipToPix(Convert.ToDouble((uint)pSize.Width));
109 | // fdoc.PageHeight = TwipToPix(Convert.ToDouble((uint)pSize.Height));
110 | //}
111 |
112 | //DocumentFormat.OpenXml.Wordprocessing.SpacingBetweenLines spLineSpacing = section.Descendants().FirstOrDefault();
113 | //// var linespacingRuleelm As OpenXmlElement = section.ChildElements.Where(Function(ce) ce.LocalName = "lineRule")(0)
114 |
115 | //if (spLineSpacing != null)
116 | //{
117 | // //MessageBox.Show("section linespacing not null");
118 | // fdoc.LineHeight = TwipToPix(Convert.ToDouble(spLineSpacing.Line));
119 | //}
120 |
121 | break;
122 |
123 |
124 | case "tbl":
125 |
126 | //DocumentFormat.OpenXml.Wordprocessing.Table wtable = (DocumentFormat.OpenXml.Wordprocessing.Table)section;
127 |
128 | //System.Windows.Documents.Table newTable = GetTable(wtable);
129 | ////System.Windows.Documents.Table newTable = GetTable(section);
130 |
131 | //foreach (TableRow tr in newTable.RowGroups[0].Rows)
132 | //{
133 | // foreach (TableCell cel in tr.Cells)
134 | // {
135 | // cel.BorderBrush = Brushes.Black;
136 | // cel.BorderThickness = new Thickness(0.3);
137 | // cel.Padding = new Thickness(10);
138 | // cel.LineHeight = fdoc.LineHeight;
139 | // }
140 | //}
141 |
142 | //fdoc.Blocks.Add(newTable);
143 | break;
144 |
145 | case "p":
146 |
147 | try
148 | {
149 | Paragraph para = GetParagraph(section);
150 | //para.FontFamily = fdoc.FontFamily;
151 | para.Margin = new Thickness(0);
152 | if (para.Inlines.Count == 0)
153 | para.Inlines.Add(new EditableRun(""));
154 | fdoc.Blocks.Add(para);
155 | }
156 | catch (Exception paraEx) { Debug.WriteLine($"Could not get paragraph:\n{paraEx.Message}"); }
157 |
158 | break;
159 |
160 | default:
161 | Debug.WriteLine("no section name");
162 | break;
163 |
164 | }
165 | }
166 |
167 | }
168 |
169 | }
170 | catch (Exception ex) { Debug.WriteLine($"Error getting flow doc: \n{ex.Message}"); }
171 |
172 | }
173 |
174 |
175 | }
176 |
177 |
178 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/WordImportExport/CreateFlowDocument/GetTable.cs:
--------------------------------------------------------------------------------
1 | //using DocumentFormat.OpenXml;
2 | //using DocumentFormat.OpenXml.Wordprocessing;
3 | //using System;
4 | //using System.Collections.Generic;
5 | //using System.Linq;
6 |
7 | //namespace AvRichTextBox;
8 |
9 | //internal static partial class WordConversions
10 | //{
11 |
12 | // internal static System.Windows.Documents.Table GetTable(DocumentFormat.OpenXml.Wordprocessing.Table wordTable)
13 | // {
14 |
15 | // var thisTable = new System.Windows.Documents.Table();
16 | // thisTable.RowGroups.Add(new TableRowGroup());
17 | // thisTable.BorderThickness = new Thickness(0.5); // initial default
18 | // thisTable.BorderBrush = Brushes.Black;
19 | // thisTable.CellSpacing = 0; // default is "2"
20 | // thisTable.Margin = new Thickness(0, 0, 0, 0); // just in case
21 | // thisTable.Padding = new Thickness(0, 0, 0, 0);
22 |
23 |
24 | // var ColumnWidths = new List();
25 | // TableGrid tgrid = wordTable.Descendants().FirstOrDefault();
26 | // int nocols = tgrid.Descendants().Count();
27 |
28 | // var LastVmergedHeadCells = new System.Windows.Documents.TableCell[nocols + 1];
29 |
30 | // thisTable.BorderBrush = Brushes.Black;
31 | // thisTable.BorderThickness = new Thickness(1);
32 |
33 | // //MessageBox.Show(wordTable.Descendants().Count().ToString());
34 |
35 | // foreach (OpenXmlElement telm in wordTable.ChildElements)
36 | // {
37 | // switch (telm.LocalName)
38 | // {
39 | // case "tblGrid":
40 |
41 | // foreach (OpenXmlElement gridelm in telm.ChildElements)
42 | // {
43 | // switch (gridelm.LocalName)
44 | // {
45 | // case "gridCol":
46 | // var newtabcol = new TableColumn();
47 | // var glc = new GridLengthConverter();
48 | // newtabcol.Width = new GridLength(TwipToPix(Convert.ToDouble(gridelm.GetAttributes()[0].Value)));
49 | // thisTable.Columns.Add(newtabcol);
50 | // break;
51 | // }
52 | // }
53 |
54 | // break;
55 |
56 | // case "tr":
57 |
58 | // var newrow = new System.Windows.Documents.TableRow();
59 | // thisTable.RowGroups[0].Rows.Add(newrow);
60 | // int colno = 0;
61 |
62 | // foreach (OpenXmlElement cn in telm.ChildElements)
63 | // {
64 | // switch (cn.LocalName)
65 | // {
66 | // case "tc":
67 |
68 | // var thiscell = new System.Windows.Documents.TableCell();
69 |
70 | // thiscell.Tag = new List(new string[] { "", "" });
71 |
72 | // foreach (OpenXmlElement CellParNode in cn.ChildElements)
73 | // {
74 | // switch (CellParNode.LocalName)
75 | // {
76 | // case "tcPr":
77 | // // Get cell properties
78 | // foreach (OpenXmlElement CellPropNode in CellParNode.ChildElements)
79 | // {
80 | // switch (CellPropNode.LocalName)
81 | // {
82 | // case "tcW":
83 | // break;
84 |
85 | // case "gridSpan":
86 | // thiscell.ColumnSpan = Convert.ToInt32(CellPropNode.GetAttributes()[0].Value);
87 | // colno += thiscell.ColumnSpan - 1;
88 | // break;
89 |
90 | // case "vAlign":
91 | // if ((CellPropNode.GetAttributes()[0].Value ?? "") == "center")
92 | // thiscell.Focusable = true;
93 | // else
94 | // thiscell.Focusable = false;
95 | // break;
96 |
97 | // case "vmerge":
98 | // case "vMerge":
99 | // if (CellPropNode.GetAttributes().Count == 0 || (CellPropNode.GetAttributes()[0].Value ?? "") == "continue")
100 | // {
101 | // LastVmergedHeadCells[colno].RowSpan += 1;
102 | // ((List)thiscell.Tag)[0] = "merged";
103 | // }
104 | // else
105 | // {
106 | // // restart
107 | // ((List)thiscell.Tag)[0] = "";
108 | // LastVmergedHeadCells[colno] = thiscell;
109 | // LastVmergedHeadCells[colno].RowSpan = 1;
110 | // }
111 |
112 | // break;
113 |
114 | // case "tcBorders":
115 | // break;
116 |
117 | // case "tcMar":
118 | // break;
119 | // }
120 | // }
121 |
122 | // break;
123 |
124 | // case "p":
125 | // thiscell.Blocks.Add(GetParagraph(CellParNode));
126 | // break;
127 | // }
128 | // }
129 |
130 | // // Add cell to current row
131 | // if (((List)thiscell.Tag)[0] != "merged")
132 | // {
133 | // newrow.Cells.Add(thiscell);
134 | // if (colno < LastVmergedHeadCells.Count())
135 | // {
136 | // LastVmergedHeadCells[colno] = thiscell;
137 | // LastVmergedHeadCells[colno].RowSpan = 1;
138 | // }
139 | // }
140 |
141 | // colno += 1;
142 | // break;
143 | // }
144 | // }
145 |
146 | // break;
147 |
148 | // }
149 | // }
150 |
151 | // return thisTable;
152 |
153 | // }
154 |
155 | //}
156 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/WordImportExport/CreateWordDocument/CreateWordDrawing.cs:
--------------------------------------------------------------------------------
1 | using A = DocumentFormat.OpenXml.Drawing;
2 | using DOW = DocumentFormat.OpenXml.Wordprocessing;
3 | using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
4 | using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
5 | using static AvRichTextBox.HelperMethods;
6 |
7 | namespace AvRichTextBox;
8 |
9 | internal static partial class WordConversions
10 | {
11 | internal static DOW.Drawing CreateWordDocDrawing(string relationshipID, double pixelWidth, double pixelHeight, string extension)
12 | {
13 | double emuWidth = PixToEMU(pixelWidth);
14 | double emuHeight = PixToEMU(pixelHeight);
15 |
16 | var drawingElement = new DOW.Drawing(new DW.Inline(new DW.Extent()
17 | {
18 | Cx = (int)emuWidth,
19 | Cy = (int)emuHeight
20 | }, new DW.EffectExtent()
21 | {
22 | LeftEdge = 0L,
23 | TopEdge = 0L,
24 | RightEdge = 0L,
25 | BottomEdge = 0L,
26 | },
27 | new DW.DocProperties() { Id = 1U, Name = "Picture1" },
28 | new DW.NonVisualGraphicFrameDrawingProperties(new A.GraphicFrameLocks() { NoChangeAspect = true }),
29 | new A.Graphic(
30 | new A.GraphicData(
31 | new PIC.Picture(
32 | new PIC.NonVisualPictureProperties(new PIC.NonVisualDrawingProperties()
33 | {
34 | Id = 0U,
35 | Name = "wordDrawing" + extension
36 | },
37 | new PIC.NonVisualPictureDrawingProperties()),
38 | new PIC.BlipFill(new A.Blip(new A.BlipExtensionList(new A.BlipExtension() { Uri = "{28A0092B-C50C-407E-A947-70E740481C1C}" }))
39 | {
40 | Embed = relationshipID,
41 | CompressionState = A.BlipCompressionValues.Print
42 | }, new A.Stretch(new A.FillRectangle())
43 | ),
44 | new PIC.ShapeProperties(
45 | new A.Transform2D(
46 | new A.Offset() { X = 0L, Y = 0L },
47 | new A.Extents() { Cx = (int)emuWidth, Cy = (int)emuHeight }
48 | ),
49 | new A.PresetGeometry(new A.AdjustValueList()) { Preset = A.ShapeTypeValues.Rectangle }
50 | )))
51 | { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" }
52 | ))
53 | {
54 | DistanceFromTop = 0U,
55 | DistanceFromBottom = 0U,
56 | DistanceFromLeft = 0U,
57 | DistanceFromRight = 0U
58 | }
59 | );
60 |
61 | return drawingElement;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/WordImportExport/CreateWordDocument/CreateWordParagraph.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Documents;
4 | using Avalonia.Media.Imaging;
5 | using DocumentFormat.OpenXml;
6 | using DocumentFormat.OpenXml.Packaging;
7 | using DocumentFormat.OpenXml.Wordprocessing;
8 | using System.Diagnostics;
9 | using System.IO;
10 | using DOW = DocumentFormat.OpenXml.Wordprocessing;
11 | using static AvRichTextBox.HelperMethods;
12 |
13 | namespace AvRichTextBox;
14 |
15 | internal static partial class WordConversions
16 | {
17 | internal static DOW.Paragraph CreateWordDocParagraph(Block b)
18 | {
19 |
20 | var parg = new DOW.Paragraph();
21 | var pPr = new ParagraphProperties();
22 | Paragraph? p = b as Paragraph;
23 |
24 | pPr.Justification = new()
25 | {
26 | Val = p!.TextAlignment switch
27 | {
28 | Avalonia.Media.TextAlignment.Left => JustificationValues.Left,
29 | Avalonia.Media.TextAlignment.Center => JustificationValues.Center,
30 | Avalonia.Media.TextAlignment.Right => JustificationValues.Right,
31 | Avalonia.Media.TextAlignment.Justify => JustificationValues.Both,
32 | _ => JustificationValues.Left
33 | }
34 | };
35 |
36 | if (p.Background!= null && p.Background.Color != Avalonia.Media.Colors.Transparent)
37 | pPr.Shading = new() { Val = ShadingPatternValues.Clear, Color = "auto", Fill = ToOpenXmlColor(p.Background.Color) };
38 |
39 | if (p.BorderBrush != null && p.BorderBrush.Color != Avalonia.Media.Colors.Transparent)
40 | {
41 | pPr.ParagraphBorders = new()
42 | {
43 | LeftBorder = new() { Val = BorderValues.Single, Color = ToOpenXmlColor(p.BorderBrush.Color), Size = (uint)(p.BorderThickness.Left * 6), Space = 0 },
44 | TopBorder = new() { Val = BorderValues.Single, Color = ToOpenXmlColor(p.BorderBrush.Color), Size = (uint)(p.BorderThickness.Top * 6), Space = 0 },
45 | RightBorder = new() { Val = BorderValues.Single, Color = ToOpenXmlColor(p.BorderBrush.Color), Size = (uint)(p.BorderThickness.Right * 6), Space = 0 },
46 | BottomBorder = new() { Val = BorderValues.Single, Color = ToOpenXmlColor(p.BorderBrush.Color), Size = (uint)(p.BorderThickness.Bottom * 6), Space = 0 },
47 | };
48 | }
49 |
50 | pPr.SpacingBetweenLines = new SpacingBetweenLines() { Before = "0", After = "0", LineRule = LineSpacingRuleValues.Auto, Line = "240" };
51 | pPr.SnapToGrid = new SnapToGrid() { Val = new OnOffValue(false) };
52 | parg.AppendChild(pPr);
53 |
54 | foreach (IEditable iline in p.Inlines)
55 | {
56 | switch (iline)
57 | {
58 | //case var @case when @case == typeof(EditableLineBreak):
59 | case EditableLineBreak elbreak:
60 | parg.AppendChild(new Break());
61 | break;
62 |
63 | case EditableInlineUIContainer edUIC:
64 |
65 | if (edUIC.Child.GetType() == typeof(Image))
66 | {
67 | //string fontFamily = edUIC.FontFamily.ToString();
68 |
69 | Image img = (Image)edUIC.Child;
70 | img.Width = img.Bounds.Width;
71 | img.Height = img.Bounds.Height;
72 | Bitmap? imgbitmap = (Bitmap)img.Source!;
73 |
74 | string extension = "";
75 | ImagePart imagePart = mainPart!.AddImagePart(ImagePartType.Jpeg);
76 |
77 | //Debug.WriteLine("Imagesource is null ? : " + (thisImg.Source == null));
78 | if (imgbitmap != null)
79 | {
80 | using (var memStream = new MemoryStream())
81 | {
82 | ResizeAndSaveBitmap(imgbitmap, (int)imgbitmap.Size.Width, (int)imgbitmap.Size.Height, memStream);
83 | memStream.Position = 0;
84 | imagePart.FeedData(memStream);
85 | extension = ".jpg";
86 | }
87 |
88 | parg.AppendChild(new DOW.Run(CreateWordDocDrawing(mainPart!.GetIdOfPart(imagePart), img.Width, img.Height, extension)));
89 |
90 | }
91 | }
92 |
93 | break;
94 |
95 | case EditableRun erun:
96 | DOW.Run dRun = GetWordDocRun(erun);
97 | //if (dRun.InnerText == "\v")
98 | // parg.AppendChild(new Break());
99 | //else
100 | parg.AppendChild(dRun);
101 | break;
102 | }
103 | }
104 |
105 | return parg;
106 | }
107 |
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/WordImportExport/CreateWordDocument/CreateWordRun.cs:
--------------------------------------------------------------------------------
1 | using DocumentFormat.OpenXml;
2 | using DocumentFormat.OpenXml.Wordprocessing;
3 | using DOW = DocumentFormat.OpenXml.Wordprocessing;
4 | using Avalonia.Media;
5 | using System;
6 | using System.Diagnostics;
7 | using static AvRichTextBox.HelperMethods;
8 | using System.Text;
9 |
10 | namespace AvRichTextBox;
11 |
12 | internal static partial class WordConversions
13 | {
14 | internal static DOW.Run GetWordDocRun(EditableRun edRun)
15 | {
16 | string? thisRunText = "";
17 | thisRunText = edRun.Text;
18 | //Debug.WriteLine("thisRuntext= " + thisRunText);
19 |
20 | var newrun = new DOW.Run();
21 |
22 | try
23 | {
24 |
25 | var runtext = new Text(thisRunText!) // convert text to "wordprocessing.text" form
26 | {
27 | Space = SpaceProcessingModeValues.Preserve
28 | };
29 |
30 | var RunProp = new RunProperties();
31 |
32 | if (edRun.TextDecorations != null)
33 | {
34 | foreach (TextDecoration td in edRun.TextDecorations!)
35 | {
36 | switch (td.Location)
37 | {
38 | case TextDecorationLocation.Underline:
39 | RunProp.AppendChild(new DOW.Underline() { Val = UnderlineValues.Single, Color = "Black" });
40 | break;
41 | case TextDecorationLocation.Overline: { break; }
42 | case TextDecorationLocation.Baseline: { break; }
43 | case TextDecorationLocation.Strikethrough:
44 | RunProp.AppendChild(new DOW.Strike());
45 | break;
46 | }
47 | }
48 | }
49 |
50 | if (edRun.FontWeight == FontWeight.Bold)
51 | RunProp.AppendChild(new DOW.Bold());
52 |
53 | if (edRun.FontStyle == FontStyle.Italic)
54 | RunProp.AppendChild(new DOW.Italic());
55 |
56 | if (edRun.Background != null)
57 | {
58 | var Hlight = new Highlight() { Val = BrushToHighlightColorValue(edRun.Background) };
59 | RunProp.AppendChild(Hlight);
60 | }
61 |
62 | if (edRun.Foreground != null)
63 | {
64 | var foreColor = new DOW.Color() { Val = edRun.Foreground.ToString() };
65 | RunProp.AppendChild(foreColor);
66 | }
67 |
68 | switch (edRun.BaselineAlignment)
69 | {
70 | case BaselineAlignment.Baseline: RunProp.AppendChild(new VerticalTextAlignment() { Val = VerticalPositionValues.Baseline }); break;
71 | case BaselineAlignment.TextTop: RunProp.AppendChild(new VerticalTextAlignment() { Val = VerticalPositionValues.Superscript }); break;
72 | case BaselineAlignment.Bottom: RunProp.AppendChild(new VerticalTextAlignment() { Val = VerticalPositionValues.Subscript }); break;
73 | }
74 |
75 | // Get font
76 | var rFont = new RunFonts();
77 |
78 |
79 |
80 | var fntSize = default(double);
81 | rFont.Ascii = edRun.FontFamily.ToString();
82 | rFont.HighAnsi = edRun.FontFamily.ToString();
83 | //rFont.EastAsia = edRun.FontFamily.ToString();
84 | rFont.EastAsia = "Meiryo UI"; // temporary default
85 |
86 | fntSize = edRun.FontSize * 0.75 * 2; // converts pixels to points
87 |
88 | RunProp.AppendChild(rFont);
89 | RunProp.AppendChild(new FontSize() { Val = fntSize.ToString() });
90 |
91 | //Attach run properties
92 | newrun.AppendChild(RunProp);
93 |
94 | // Must parse line breaks
95 | if (!string.IsNullOrEmpty(runtext.Text))
96 | {
97 | //if (runtext.Text.Contains(Constants.vbLf))
98 | if (runtext.Text.Contains('\n'))
99 | ParseRunText(ref newrun, runtext.Text);
100 | else
101 |
102 | newrun.AppendChild(runtext);
103 | }
104 |
105 | }
106 | catch (Exception ex) { Debug.WriteLine($"Failed to create run: {edRun.Text}\nexception: {ex.Message}"); }
107 |
108 | return newrun;
109 | }
110 |
111 | public static void ParseRunText(ref DOW.Run r, string tData)
112 | {
113 | //var newLineArray = new[] { Constants.vbLf };
114 | //var textArray = tData.Split(newLineArray, StringSplitOptions.None);
115 | var textArray = tData.Split("\n".ToCharArray(), StringSplitOptions.None);
116 |
117 | foreach (string line in textArray)
118 | {
119 | if (string.IsNullOrEmpty(line))
120 | {
121 | if (r.LastChild is not Break)
122 | r.AppendChild(new Break());
123 | }
124 | else
125 | {
126 | var txt = new Text
127 | {
128 | Text = line
129 | };
130 | r.AppendChild(txt);
131 | }
132 | }
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/WordImportExport/CreateWordDocument/CreateWordTable.cs:
--------------------------------------------------------------------------------
1 | //using DocumentFormat.OpenXml;
2 | //using DocumentFormat.OpenXml.Packaging;
3 | //using DocumentFormat.OpenXml.Wordprocessing;
4 | //using System.Diagnostics;
5 | //using System.Windows.Media.Imaging;
6 | //using System.Xml;
7 | //using DOW = DocumentFormat.OpenXml.Wordprocessing;
8 |
9 | //namespace AvRichTextBox;
10 |
11 | //internal static partial class WordConversions
12 | //{
13 | // static bool firstTable = true;
14 |
15 | // internal static DOW.Table CreateWordDocTable(System.Windows.Documents.Table t)
16 | // {
17 |
18 | // var tabl = new DOW.Table();
19 |
20 | // List VMergeCount = new List();
21 | // for (int i = 0; i < 100; i++) VMergeCount.Add(new Point(1, 0));
22 |
23 | // // Add column definitions
24 | // var tblGrid = new TableGrid();
25 | // for (int colno = 0; colno < t.Columns.Count; colno++)
26 | // tblGrid.Append(new GridColumn() { Width = PixToTwip(t.Columns[colno].Width.Value).ToString() });
27 | // tabl.Append(tblGrid);
28 |
29 | // int coloffset = 0;
30 |
31 | // foreach (TableRowGroup rgroup in t.RowGroups)
32 | // {
33 | // foreach (System.Windows.Documents.TableRow r in rgroup.Rows)
34 | // {
35 | // var trow = new DOW.TableRow();
36 | // tabl.AppendChild(trow);
37 | // coloffset = 0;
38 |
39 | // for (int colno = 0; colno < t.Columns.Count; colno++)
40 | // {
41 |
42 | // var cel = new DOW.TableCell();
43 | // trow.Append(cel);
44 | // var tcprop = new TableCellProperties();
45 | // cel.Append(tcprop);
46 |
47 |
48 | // var tcwid = new TableCellWidth() { Width = PixToTwip(t.Columns[colno].Width.Value).ToString() };
49 | // tcprop.Append(tcwid);
50 |
51 | // var tcbor = new TableCellBorders();
52 | // tcprop.Append(tcbor);
53 |
54 | // if (VMergeCount[colno].X > 1)
55 | // {
56 | // tcprop.Append(new VerticalMerge() { Val = new EnumValue(MergedCellValues.Continue) });
57 | // tcprop.Append(new GridSpan() { Val = (Int32Value)(VMergeCount[colno].Y + 1) });
58 |
59 | // VMergeCount[colno] = new Point(VMergeCount[colno].X - 1, VMergeCount[colno].Y);
60 |
61 | // colno += (int)VMergeCount[colno].Y;
62 | // coloffset += (int)VMergeCount[colno].Y + 1;
63 |
64 | // var pprop = new ParagraphProperties() { SpacingBetweenLines = new SpacingBetweenLines() { Line = "240", LineRule = LineSpacingRuleValues.Auto, Before = "0", After = "0" } };
65 | // var newpar = new DOW.Paragraph();
66 | // newpar.Append(pprop);
67 |
68 | // cel.Append(newpar); // necessary to add paragraph to vmerged cell
69 | // }
70 | // else
71 | // {
72 | // tcprop.Append(new VerticalMerge() { Val = new EnumValue(MergedCellValues.Restart) });
73 |
74 | // foreach (Block b in r.Cells[colno - coloffset].Blocks)
75 | // cel.Append(CreateWordDocParagraph((System.Windows.Documents.Paragraph)b));
76 |
77 | // if (r.Cells[colno - coloffset].RowSpan > 1)
78 | // VMergeCount[colno] = new Point(r.Cells[colno - coloffset].RowSpan, r.Cells[colno - coloffset].ColumnSpan - 1);
79 |
80 | // if (r.Cells[colno - coloffset].ColumnSpan > 1)
81 | // tcprop.GridSpan = new GridSpan() { Val = r.Cells[colno - coloffset].ColumnSpan };
82 | // else
83 | // VMergeCount[colno] = new Point(VMergeCount[colno].X, 0);
84 |
85 | // var thisCellVertAlign = new TableCellVerticalAlignment() { Val = TableVerticalAlignmentValues.Top };
86 | // switch (r.Cells[colno - coloffset].BorderThickness.Left)
87 | // {
88 | // case 0.49:
89 | // thisCellVertAlign.Val = TableVerticalAlignmentValues.Center;
90 | // break;
91 | // case 0.48:
92 | // thisCellVertAlign.Val = TableVerticalAlignmentValues.Bottom;
93 | // break;
94 | // }
95 | // tcprop.Append(thisCellVertAlign);
96 |
97 |
98 | // var lmar = new LeftMargin() { Width = PixToTwip(r.Cells[colno - coloffset].Padding.Left).ToString() };
99 | // var tmar = new TopMargin() { Width = PixToTwip(r.Cells[colno - coloffset].Padding.Top).ToString() };
100 | // var rmar = new RightMargin() { Width = PixToTwip(r.Cells[colno - coloffset].Padding.Right).ToString() };
101 | // var bmar = new BottomMargin() { Width = PixToTwip(r.Cells[colno - coloffset].Padding.Bottom).ToString() };
102 | // var tcmar = new TableCellMargin() { LeftMargin = lmar, TopMargin = tmar, RightMargin = rmar, BottomMargin = bmar };
103 | // tcprop.Append(tcmar);
104 |
105 | // int thiscolspan = r.Cells[colno - coloffset].ColumnSpan;
106 | // VMergeCount[colno] = new Point(VMergeCount[colno].X, thiscolspan - 1);
107 | // colno += thiscolspan - 1;
108 | // coloffset += thiscolspan - 1;
109 | // }
110 |
111 | // tcbor.Append(new LeftBorder() { Size = 7, Color = "#000000", Val = BorderValues.Single });
112 | // tcbor.Append(new RightBorder() { Size = 7, Color = "#000000", Val = BorderValues.Single });
113 | // tcbor.Append(new TopBorder() { Size = 7, Color = "#000000", Val = BorderValues.Single });
114 | // tcbor.Append(new BottomBorder() { Size = 7, Color = "#000000", Val = BorderValues.Single});
115 | // }
116 | // }
117 | // }
118 |
119 | // return tabl;
120 | // }
121 |
122 |
123 | //}
124 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/WordImportExport/CreateWordDocument/WordDocumentCreator.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Media;
2 | using DocumentFormat.OpenXml;
3 | using DocumentFormat.OpenXml.Packaging;
4 | using DocumentFormat.OpenXml.Wordprocessing;
5 | using System;
6 | using System.Diagnostics;
7 | using DOW = DocumentFormat.OpenXml.Wordprocessing;
8 | using AvColor = Avalonia.Media.Color;
9 | using static AvRichTextBox.HelperMethods;
10 |
11 | namespace AvRichTextBox;
12 |
13 | internal static partial class WordConversions
14 | {
15 | private static MainDocumentPart? mainPart;
16 |
17 | internal static void SaveWordDoc(string saveWordFileName, FlowDocument fdoc)
18 | {
19 | try
20 | {
21 | using var WPDoc = WordprocessingDocument.Create(saveWordFileName, WordprocessingDocumentType.Document);
22 |
23 | mainPart = WPDoc.AddMainDocumentPart();
24 | mainPart.Document = new Document();
25 |
26 |
27 | //Set page and margin size according to Client
28 | int pmarLeft = default, pmarTop = default, pmarRight = default, pmarBottom = default;
29 | pmarLeft = Convert.ToInt32(PixToTwip(fdoc.PagePadding.Left));
30 | pmarTop = Convert.ToInt32(PixToTwip(fdoc.PagePadding.Top));
31 | pmarRight = Convert.ToInt32(PixToTwip(fdoc.PagePadding.Right));
32 | pmarBottom = Convert.ToInt32(PixToTwip(fdoc.PagePadding.Bottom));
33 | var newpageMargin = new PageMargin() { Left = Convert.ToUInt32(pmarLeft), Top = pmarTop, Bottom = pmarBottom, Right = Convert.ToUInt32(pmarRight), Header = 720, Footer = 720 };
34 |
35 | var newcol = new Columns() { Space = new StringValue("720") }; // (StringValue)720 };
36 | var newpageSize = new PageSize() { Width = 21 * 567, Height = Convert.ToUInt32(29.7 * 567) }; // A4 size
37 |
38 |
39 | //Start creating word body
40 | var body = new Body();
41 | var sectp = new SectionProperties();
42 | sectp.Append(newpageMargin);
43 | sectp.Append(newpageSize);
44 | body.Append(sectp);
45 |
46 |
47 | mainPart.Document.AppendChild(body);
48 |
49 | foreach (Block b in fdoc.Blocks)
50 | {
51 | switch (b.GetType())
52 | {
53 | case Type t when t == typeof(Paragraph):
54 |
55 | Paragraph p = (Paragraph)b;
56 | DOW.Paragraph dowP = CreateWordDocParagraph(p);
57 | body.AppendChild(dowP);
58 |
59 | //Debug.WriteLine("\npPR.spacing=" + dowP.ParagraphProperties.SpacingBetweenLines.Line.ToString() + " (" + dowP.ParagraphProperties.SpacingBetweenLines.LineRule.ToString() + ")");
60 |
61 | break;
62 |
63 | //case Type t when t == typeof(Table):
64 | // body.AppendChild(CreateWordDocTable((Table)b));
65 | // break;
66 | }
67 | }
68 |
69 | WPDoc.Save();
70 |
71 |
72 | }
73 | catch (Exception ex) { Debug.WriteLine("Could not save: " + ex.GetType() + " /// " + ex.Message); }
74 |
75 |
76 | }
77 |
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/WordImportExport/HelperMethods.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Media;
3 | using Avalonia.Media.Imaging;
4 | using Avalonia.Media.Immutable;
5 | using DocumentFormat.OpenXml.Wordprocessing;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Diagnostics;
9 | using System.IO;
10 | using System.Linq;
11 | using System.Text.RegularExpressions;
12 | using AvColor = Avalonia.Media.Color;
13 |
14 | namespace AvRichTextBox;
15 |
16 | internal static partial class HelperMethods
17 | {
18 | internal static AvColor ColorFromHex(string hex) => AvColor.Parse(hex);
19 | internal static double TwipToPix(double unitTwip) => 96.0 / 1440D * unitTwip;
20 | internal static double PixToTwip(double unitPix) => 15D * unitPix;
21 | internal static double EMUToPix(double unitTwip) => 96 / (double)914400 * unitTwip;
22 | internal static double PixToEMU(double unitPix) => 914400 / (double)96 * unitPix;
23 | internal static double PointsToPixels(double pt) => pt * 96D / 72D;
24 | internal static double InchesToPixels(double inch) => inch * 96D;
25 | internal static double PixelsToPoints(double px) => px * 72 / 96;
26 | internal static double TwipToDip(double twips) => twips * (96.0 / 1440.0);
27 | internal static double DipToTwip(double dips) => dips * (1440.0 / 96.0);
28 |
29 | internal static void ResizeAndSaveBitmap(Bitmap originalBitmap, int newWidth, int newHeight, Stream memoryStream)
30 | {
31 | var renderTarget = new RenderTargetBitmap(new PixelSize(newWidth, newHeight));
32 |
33 | using (var context = renderTarget.CreateDrawingContext())
34 | {
35 | context.DrawImage(originalBitmap, new Rect(0, 0, newWidth, newHeight));
36 | }
37 |
38 | renderTarget.Save(memoryStream); // png by default
39 | }
40 |
41 | internal static string WordColorValueToHexString(string hcv, bool isBackground)
42 | {
43 | string hex = hcv.ToLower();
44 |
45 | string returnString = hex switch
46 | {
47 | "darkyellow" => "#FF9ACD32",
48 | "darkred" => "#FF8B000000",
49 | "blueviolet" => "#FF931FDF",
50 | "cyan" => "#FFE8EBF9",
51 | "green" => "#FF98FB98",
52 | "yellow" => "#FFFFE0C0",
53 | "red" => "#FFFF4500",
54 | "blue" => "#FFE3BFFF",
55 | "black" or "ck" => "#FF000000",
56 | "lightgray" => "#FFCCCCCC",
57 | "magenta" => "#FFFF00FF",
58 | "white" => "#FFFFFFFF",
59 | "none" => "#00FFFFFF",
60 | _ => string.Empty
61 | };
62 |
63 | if (returnString != string.Empty) return returnString;
64 | //return hexColorRegex().Replace(hex, "") == "" ? ("#" + hex) : (isBackground ? "#FFFFFFFF" : "#FF000000"); // default white or black
65 | return hexColorRegex().Replace(hex, "") == "" ? (hex) : (isBackground ? "#FFFFFFFF" : "#FF000000"); // default white or black
66 |
67 | }
68 |
69 | internal static HighlightColorValues BrushToHighlightColorValue(IBrush br)
70 | {
71 | if (br is not SolidColorBrush solidColorBrush || br == Brushes.Transparent)
72 | return HighlightColorValues.None;
73 |
74 | var inputColor = solidColorBrush.Color;
75 |
76 | // Check for direct matches first
77 | var predefinedMatch = HighlightColors.FirstOrDefault(kv => kv.Value == inputColor);
78 | if (HighlightColors.ContainsKey(predefinedMatch.Key))
79 | {
80 | Debug.WriteLine("key = " + predefinedMatch.Key.ToString());
81 | return predefinedMatch.Key;
82 | }
83 |
84 |
85 | return FindClosestHighlightColor(inputColor);
86 |
87 | }
88 |
89 | private static readonly Dictionary HighlightColors = new()
90 | {
91 | { HighlightColorValues.Yellow, Colors.Yellow },
92 | { HighlightColorValues.Red, Colors.Red },
93 | { HighlightColorValues.Cyan, Colors.Cyan },
94 | { HighlightColorValues.Green, Colors.Green },
95 | { HighlightColorValues.Blue, Colors.Blue },
96 | { HighlightColorValues.DarkYellow, Colors.YellowGreen },
97 | { HighlightColorValues.White, Colors.White },
98 | { HighlightColorValues.Magenta, Colors.Magenta },
99 | { HighlightColorValues.LightGray, Colors.LightGray },
100 | { HighlightColorValues.DarkRed, Colors.DarkRed },
101 | { HighlightColorValues.DarkMagenta, Colors.DarkMagenta },
102 | { HighlightColorValues.DarkGreen, Colors.DarkGreen },
103 | { HighlightColorValues.DarkGray, Colors.DarkGray },
104 | { HighlightColorValues.DarkCyan, Colors.DarkCyan },
105 | { HighlightColorValues.DarkBlue, Colors.DarkBlue },
106 | { HighlightColorValues.Black, Colors.Black },
107 | { HighlightColorValues.None, Colors.Transparent }
108 | };
109 |
110 | internal static string ToOpenXmlColor(AvColor color) => $"{color.R:X2}{color.G:X2}{color.B:X2}";
111 |
112 | internal static SolidColorBrush FromOpenXmlColor(string openxmlhex)
113 | {
114 | BrushConverter BConverter = new();
115 | ImmutableSolidColorBrush? iSCB = (ImmutableSolidColorBrush)BConverter.ConvertFromString("#" + openxmlhex)!;
116 | return new SolidColorBrush(iSCB.Color);
117 |
118 | }
119 |
120 | private static HighlightColorValues FindClosestHighlightColor(AvColor color)
121 | {
122 | return HighlightColors.OrderBy(kv => ColorDistance(color, kv.Value)).First().Key;
123 | }
124 |
125 | private static double ColorDistance(AvColor c1, AvColor c2)
126 | {
127 | int rDiff = c1.R - c2.R;
128 | int gDiff = c1.G - c2.G;
129 | int bDiff = c1.B - c2.B;
130 | return Math.Sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff);
131 | }
132 |
133 | internal static int GetLanguageForChar(char c)
134 | {
135 | if (c is >= (char)0x4E00 and <= (char)0x9FFF or // CJK Unified Ideographs (Common Chinese, Japanese, Korean)
136 | >= (char)0x3400 and <= (char)0x4DBF) // CJK Extension A (Rare Chinese characters)
137 | return 2052; // Simplified Chinese (zh-CN)
138 | else if (c is >= (char)0x3040 and <= (char)0x30FF) // Hiragana & Katakana (Japanese)
139 | return 1041; // Japanese (ja-JP)
140 | else if (c is >= (char)0xAC00 and <= (char)0xD7AF) // Hangul Syllables (Korean)
141 | return 1042; // Korean (ko-KR)
142 | else if (c is >= (char)0x3100 and <= (char)0x312F) // Bopomofo (Traditional Chinese phonetic)
143 | return 1028; // Traditional Chinese (zh-TW)
144 | else
145 | return 1033; // Default to English (en-US)
146 | }
147 |
148 | internal static bool IsCJKChar(char c) => GetLanguageForChar(c) is 2052 or 1041 or 1042 or 1028;
149 | [GeneratedRegex("[#0-9a-f]")]
150 | private static partial Regex hexColorRegex();
151 | }
152 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/Xaml/XamlPackageData/.rels:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/Xaml/XamlPackageData/[Content_Types].xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/AvRichTextBox/SaveAndLoadFormats/Xaml/XamlStringFromFlowDoc.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cuikp/AvRichTextBox/145e48b7aecf1b896dc4a5fd573061b5bcd449d9/AvRichTextBox/SaveAndLoadFormats/Xaml/XamlStringFromFlowDoc.cs
--------------------------------------------------------------------------------
/AvRichTextBox/TextRange/TextRange.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Input;
3 | using DocumentFormat.OpenXml.Drawing.Charts;
4 | using System;
5 | using System.ComponentModel;
6 | using System.Data.SqlTypes;
7 | using System.Diagnostics;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Runtime.CompilerServices;
11 | using System.Text;
12 | using System.Threading.Tasks;
13 |
14 | namespace AvRichTextBox;
15 |
16 | public class TextRange : INotifyPropertyChanged, IDisposable
17 | {
18 | public event PropertyChangedEventHandler? PropertyChanged;
19 | private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
20 |
21 | internal delegate void Start_ChangedHandler(TextRange sender, int newStart);
22 | internal event Start_ChangedHandler? Start_Changed;
23 | internal delegate void End_ChangedHandler(TextRange sender, int newEnd);
24 | internal event End_ChangedHandler? End_Changed;
25 |
26 | public override string ToString() => this.Start + " → " + this.End;
27 |
28 | public TextRange(FlowDocument flowdoc, int start, int end)
29 | {
30 | if (end < start) throw new AvaloniaInternalException("TextRange not valid (start must be less than end)");
31 |
32 | this.Start = start;
33 | this.End = end;
34 | myFlowDoc = flowdoc;
35 | myFlowDoc.TextRanges.Add(this);
36 |
37 | }
38 |
39 | internal FlowDocument myFlowDoc;
40 | public int Length => End - Start;
41 | private int _Start;
42 | public int Start { get => _Start; set { if (_Start != value) { _Start = value; Start_Changed?.Invoke(this, value); NotifyPropertyChanged(nameof(Start)); } } }
43 |
44 | private int _End;
45 | public int End { get => _End; set { if (_End != value) { _End = value; End_Changed?.Invoke(this, value); NotifyPropertyChanged(nameof(End)); } } }
46 |
47 | internal void UpdateStart() { NotifyPropertyChanged(nameof(Start)); }
48 | internal void UpdateEnd() { NotifyPropertyChanged(nameof(End)); }
49 |
50 | internal Paragraph StartParagraph = null!;
51 | internal Paragraph EndParagraph = null!;
52 |
53 | internal Rect PrevCharRect;
54 | internal Rect StartRect { get; set; }
55 | internal Rect EndRect { get; set; }
56 | internal bool IsAtEndOfLineSpace = false;
57 | internal bool IsAtEndOfLine = false;
58 | internal bool IsAtLineBreak = false;
59 |
60 | internal bool BiasForwardStart = true;
61 | internal bool BiasForwardEnd = true;
62 | public void CollapseToStart() { End = Start; }
63 | public void CollapseToEnd() { Start = End ; }
64 |
65 |
66 | internal IEditable GetStartInline()
67 | {
68 |
69 | Paragraph? startPar = GetStartPar();
70 | if (startPar == null) return null!;
71 | IEditable startInline = null!;
72 | IsAtLineBreak = false;
73 |
74 | if (BiasForwardStart)
75 | {
76 | IEditable startInlineReal = startPar.Inlines.LastOrDefault(ied => startPar.StartInDoc + ied.TextPositionOfInlineInParagraph <= Start)!;
77 | startInline = startPar.Inlines.LastOrDefault(ied => !ied.IsLineBreak && startPar.StartInDoc + ied.TextPositionOfInlineInParagraph <= Start)!;
78 | IsAtLineBreak = startInline != startInlineReal;
79 | //Debug.WriteLine("calculating isatlinebreak biasforwardstart");
80 | }
81 | else
82 | {
83 | if (Start - startPar.StartInDoc == 0)
84 | startInline = startPar.Inlines.FirstOrDefault()!;
85 | else
86 | {
87 | //Debug.WriteLine("calculating isatlinebreak - OTHER");
88 | startInline = startPar.Inlines.LastOrDefault(ied => startPar.StartInDoc + ied.TextPositionOfInlineInParagraph < Start)!;
89 | IEditable startInlineUpToLineBreak = startPar.Inlines.LastOrDefault(ied => !ied.IsLineBreak && startPar.StartInDoc + ied.TextPositionOfInlineInParagraph < Start)!;
90 | if (startInline.IsLineBreak)
91 | startInline = myFlowDoc.GetNextInline(startInline) ?? startInline;
92 | IsAtLineBreak = startInline != startInlineUpToLineBreak;
93 | }
94 | }
95 |
96 | return startInline!;
97 |
98 | }
99 |
100 |
101 | internal IEditable GetEndInline()
102 | {
103 | Paragraph? endPar = GetEndPar();
104 | if (endPar == null) return null!;
105 |
106 | IEditable endInline = null!;
107 |
108 | //if (trange.BiasForwardStart && trange.Length == 0)
109 | if (BiasForwardStart)
110 | endInline = endPar.Inlines.LastOrDefault(ied => endPar.StartInDoc + ied.TextPositionOfInlineInParagraph <= End)!;
111 | else
112 | endInline = endPar.Inlines.LastOrDefault(ied => endPar.StartInDoc + ied.TextPositionOfInlineInParagraph < End)!;
113 |
114 | return endInline!;
115 | }
116 |
117 |
118 | public Paragraph? GetStartPar()
119 | {
120 | Paragraph? startPar = myFlowDoc.Blocks.LastOrDefault(b => b.IsParagraph && (b.StartInDoc <= Start))! as Paragraph;
121 |
122 | if (startPar != null)
123 | {
124 | //Check if start at end of last paragraph (cannot span from end of a paragraph)
125 | if (startPar != myFlowDoc.Blocks.Where(b => b.IsParagraph).Last() && startPar!.EndInDoc == Start)
126 | startPar = myFlowDoc.Blocks.FirstOrDefault(b => b.IsParagraph && myFlowDoc.Blocks.IndexOf(b) > myFlowDoc.Blocks.IndexOf(startPar))! as Paragraph;
127 | }
128 |
129 | return startPar;
130 |
131 | }
132 |
133 | public Paragraph? GetEndPar()
134 | {
135 | return myFlowDoc.Blocks.LastOrDefault(b => b.IsParagraph && b.StartInDoc < End)! as Paragraph; // less than to keep within emd of paragraph
136 |
137 | }
138 |
139 | public object? GetFormatting(AvaloniaProperty avProp)
140 | {
141 | object? formatting = null!;
142 | if (myFlowDoc == null) return null!;
143 | IEditable currentInline = GetStartInline();
144 | if (currentInline != null)
145 | formatting = myFlowDoc.GetFormattingInline(avProp, currentInline);
146 |
147 | return formatting;
148 | }
149 |
150 | bool isFormatting = false;
151 | public void ApplyFormatting(AvaloniaProperty avProp, object value)
152 | {
153 | if (myFlowDoc == null) return;
154 | if (Length < 1) return;
155 |
156 | //try
157 | //{
158 | // Debug.WriteLine("\napplying: " + (this.Text ?? "null"));
159 | //}
160 | //catch (Exception ex) { Debug.WriteLine("exception, length = " + this.Length + " :::start = " + this.Start + " ::: end= " + this.End + " :::" + ex.Message); }
161 | if (this.Text == "") return;
162 |
163 | myFlowDoc.ApplyFormattingRange(avProp, value, this);
164 |
165 | BiasForwardStart = false;
166 | BiasForwardEnd = false;
167 |
168 | }
169 |
170 | internal string GetText()
171 | {
172 | if (myFlowDoc == null) return "";
173 | return myFlowDoc.GetText(this);
174 | }
175 |
176 | public string Text
177 | {
178 | get => GetText();
179 | set => myFlowDoc.SetRangeToText(this, value);
180 | }
181 |
182 | public void Dispose()
183 | {
184 | Dispose(true);
185 | GC.SuppressFinalize(this);
186 |
187 | }
188 |
189 | private bool _disposed = false;
190 | protected virtual void Dispose(bool disposing)
191 | {
192 | if (_disposed)
193 | return;
194 |
195 | if (disposing)
196 | {
197 | StartParagraph = null!;
198 | EndParagraph = null!;
199 |
200 | Start_Changed = null;
201 | End_Changed = null;
202 | this.Start = 0; this.End = 0;
203 | myFlowDoc.TextRanges.Remove(this);
204 | myFlowDoc = null!;
205 |
206 | }
207 | _disposed = true;
208 | }
209 |
210 |
211 |
212 | }
213 |
214 |
--------------------------------------------------------------------------------
/AvRichTextBox/Undo/IUndo.cs:
--------------------------------------------------------------------------------
1 | namespace AvRichTextBox;
2 |
3 | public interface IUndo
4 | {
5 | public void PerformUndo();
6 | public int UndoEditOffset { get; }
7 | public bool UpdateTextRanges { get; }
8 | }
9 |
10 | internal class IEditablePropertyAssociation
11 | {
12 | internal IEditable InlineItem { get; set; }
13 | internal object PropertyValue { get; set; }
14 | internal FlowDocument.FormatRun? FormatRun { get; set; }
15 |
16 | internal IEditablePropertyAssociation(IEditable inlineItem, FlowDocument.FormatRun formatRun, object propertyValue)
17 | {
18 | InlineItem = inlineItem;
19 | FormatRun = formatRun;
20 | PropertyValue = propertyValue;
21 | }
22 | }
--------------------------------------------------------------------------------
/AvRichTextBox/Undo/Undos.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls.Documents;
2 | using DynamicData;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 |
7 | namespace AvRichTextBox;
8 |
9 | internal class InsertCharUndo (int insertParIndex, int insertInlineIdx, int insertPos, FlowDocument flowDoc, int origSelectionStart) : IUndo
10 | {
11 | public int UndoEditOffset => -1;
12 | public bool UpdateTextRanges => true;
13 |
14 | public void PerformUndo()
15 | {
16 | try
17 | {
18 | Paragraph thisPar = (Paragraph)flowDoc.Blocks[insertParIndex];
19 | Run? thisRun = thisPar.Inlines[insertInlineIdx] as Run;
20 | thisRun!.Text = thisRun.Text!.Remove(insertPos, 1);
21 | thisPar.CallRequestInlinesUpdate();
22 | flowDoc.UpdateBlockAndInlineStarts(insertParIndex);
23 | flowDoc.Selection.Start = origSelectionStart;
24 | flowDoc.Selection.End = flowDoc.Selection.Start;
25 | }
26 | catch { Debug.WriteLine("Failed InsertCharUndo at inline idx: " + insertInlineIdx); }
27 |
28 | }
29 | }
30 |
31 | internal class DeleteCharUndo(int deleteParIndex, int deleteInlineIdx, IEditable deletedRun, string deleteChar, int deletePos, FlowDocument flowDoc, int origSelectionStart) : IUndo
32 | {
33 | public int UndoEditOffset => 1;
34 | public bool UpdateTextRanges => true;
35 | internal int DeleteInlineIdx => deleteInlineIdx;
36 |
37 | public void PerformUndo()
38 | {
39 | try
40 | {
41 | Paragraph thisPar = (Paragraph)flowDoc.Blocks[deleteParIndex];
42 | if (deletedRun != null)
43 | thisPar.Inlines.Insert(deleteInlineIdx, deletedRun);
44 | else
45 | {
46 | Run? thisRun = thisPar.Inlines[deleteInlineIdx] as Run;
47 | thisRun!.Text = thisRun.Text!.Insert(deletePos, deleteChar);
48 | }
49 |
50 | thisPar.CallRequestInlinesUpdate();
51 | flowDoc.UpdateBlockAndInlineStarts(deleteParIndex);
52 | flowDoc.Selection.Start = origSelectionStart;
53 | flowDoc.Selection.End = flowDoc.Selection.Start;
54 | }
55 | catch { Debug.WriteLine("Failed DeleteCharUndo at delete pos: " + deletePos); }
56 | }
57 |
58 | }
59 |
60 | internal class DeleteImageUndo(int deleteParIndex, IEditable deletedIUC, int deletedInlineIdx, FlowDocument flowDoc, int origSelectionStart, bool emptyRunAdded) : IUndo
61 | {
62 | public int UndoEditOffset => 1;
63 | public bool UpdateTextRanges => true;
64 |
65 | public void PerformUndo()
66 | {
67 | try
68 | {
69 | Paragraph thisPar = (Paragraph)flowDoc.Blocks[deleteParIndex];
70 | if (emptyRunAdded)
71 | thisPar.Inlines.RemoveAt(deletedInlineIdx);
72 | thisPar.Inlines.Insert(deletedInlineIdx, deletedIUC);
73 | thisPar.CallRequestInlinesUpdate();
74 | flowDoc.UpdateBlockAndInlineStarts(deleteParIndex);
75 | flowDoc.Selection.Start = origSelectionStart;
76 | flowDoc.Selection.End = flowDoc.Selection.Start;
77 | }
78 | catch { Debug.WriteLine("Failed DeleteImageUndo at delete pos: " + origSelectionStart); }
79 | }
80 |
81 | }
82 |
83 | internal class PasteUndo(Dictionary> keptParsAndInlines, int parIndex, FlowDocument flowDoc, int origSelectionStart, int undoEditOffset) : IUndo
84 | {
85 | public int UndoEditOffset => undoEditOffset;
86 | public bool UpdateTextRanges => true;
87 |
88 | public void PerformUndo()
89 | {
90 | try
91 | {
92 | flowDoc.RestoreDeletedBlocks(keptParsAndInlines, parIndex);
93 |
94 | flowDoc.Selection.Start = 0; //??? why necessary for cursor?
95 | flowDoc.Selection.End = 0;
96 | flowDoc.Selection.Start = origSelectionStart;
97 | flowDoc.Selection.End = origSelectionStart;
98 | flowDoc.UpdateSelection();
99 | }
100 | catch { Debug.WriteLine("Failed PasteUndo at OrigSelectionStart: " + origSelectionStart); }
101 | }
102 | }
103 |
104 | internal class DeleteRangeUndo (Dictionary> keptParsAndInlines, int parIndex, FlowDocument flowDoc, int origSelectionStart, int undoEditOffset) : IUndo
105 | { //parInlines are cloned inlines
106 |
107 | public int UndoEditOffset => undoEditOffset;
108 | public bool UpdateTextRanges => true;
109 |
110 | public void PerformUndo()
111 | {
112 | try
113 | {
114 | flowDoc.RestoreDeletedBlocks(keptParsAndInlines, parIndex);
115 |
116 | flowDoc.Selection.Start = 0; //??? why necessary for cursor?
117 | flowDoc.Selection.End = 0;
118 | flowDoc.Selection.Start = origSelectionStart;
119 | flowDoc.Selection.End = origSelectionStart;
120 |
121 | flowDoc.UpdateSelection();
122 | }
123 | catch { Debug.WriteLine("Failed DeleteRangeUndo at ParIndex: " + parIndex); }
124 | }
125 |
126 | }
127 |
128 |
129 | internal class InsertParagraphUndo (FlowDocument flowDoc, int insertedParIndex, List keepParInlines, int origSelectionStart, int undoEditOffset) : IUndo
130 | {
131 | public int UndoEditOffset => undoEditOffset;
132 | public bool UpdateTextRanges => true;
133 |
134 | public void PerformUndo()
135 | {
136 | try
137 | {
138 | Paragraph insertedPar = (Paragraph)flowDoc.Blocks[insertedParIndex];
139 | insertedPar.Inlines.Clear();
140 | insertedPar.Inlines.AddRange(keepParInlines);
141 | flowDoc.Blocks.RemoveAt(insertedParIndex + 1);
142 | flowDoc.UpdateBlockAndInlineStarts(insertedParIndex);
143 | //flowDoc.MergeParagraphForward(insertedIndex, false, origSelectionStart);
144 | flowDoc.Selection.Start = origSelectionStart;
145 | flowDoc.Selection.End = flowDoc.Selection.Start;
146 | }
147 | catch { Debug.WriteLine("Failed InsertParagraphUndo at InsertedIndex: " + insertedParIndex); }
148 |
149 | }
150 | }
151 |
152 | internal class MergeParagraphUndo (int origMergedParInlinesCount, int mergedParIndex, Paragraph removedPar, FlowDocument flowDoc, int originalSelectionStart) : IUndo
153 | { //removedPar is a clone
154 |
155 | public int UndoEditOffset => 1;
156 | public bool UpdateTextRanges => false;
157 |
158 | public void PerformUndo()
159 | {
160 | try
161 | {
162 | //flowDoc.InsertParagraph(false, mergedCharIndex);
163 |
164 | Paragraph mergedPar = (Paragraph)flowDoc.Blocks[mergedParIndex];
165 |
166 | for (int rno = mergedPar.Inlines.Count - 1; rno >= origMergedParInlinesCount; rno--)
167 | mergedPar.Inlines.RemoveAt(rno);
168 |
169 | flowDoc.Blocks.Insert(mergedParIndex + 1, removedPar);
170 |
171 | flowDoc.UpdateBlockAndInlineStarts(mergedParIndex);
172 | flowDoc.Selection.End = originalSelectionStart;
173 | flowDoc.Selection.Start = originalSelectionStart;
174 |
175 | }
176 | catch { Debug.WriteLine("Failed MergeParagraphUndo at MergedParIndex: " + mergedParIndex); }
177 | }
178 | }
179 |
180 |
181 | internal class ApplyFormattingUndo (FlowDocument flowDoc, List propertyAssociations, int originalSelection, TextRange tRange) : IUndo
182 | {
183 | public int UndoEditOffset => 0;
184 | public bool UpdateTextRanges => false;
185 |
186 | public void PerformUndo()
187 | {
188 |
189 | foreach (IEditablePropertyAssociation propassoc in propertyAssociations)
190 | if (propassoc.FormatRun != null)
191 | flowDoc.ApplyFormattingInline(propassoc.FormatRun, propassoc.InlineItem, propassoc.PropertyValue);
192 |
193 | foreach (Paragraph p in flowDoc.GetRangeBlocks(tRange).Where(b=>b.IsParagraph))
194 | p.CallRequestInlinesUpdate();
195 |
196 | flowDoc.Selection.Start = originalSelection;
197 | flowDoc.Selection.End = originalSelection;
198 |
199 | }
200 | }
201 |
202 |
203 |
--------------------------------------------------------------------------------
/AvRichTextBox/UniqueBitmap.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.Documents;
3 | using Avalonia.Media.Imaging;
4 | using System.Collections.Generic;
5 | using System.ComponentModel;
6 | using System.Runtime.CompilerServices;
7 |
8 | namespace AvRichTextBox;
9 |
10 | public class UniqueBitmap(Bitmap ubmap, int w, int h, int cIndex)
11 | {
12 | internal Bitmap uBitmap = ubmap;
13 | internal int maxWidth = w;
14 | internal int maxHeight = h;
15 | internal int consecutiveIndex = cIndex;
16 |
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/AvRichTextBox/VisualHelper.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Styling;
3 | using Avalonia.VisualTree;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Linq;
7 |
8 | namespace AvRichTextBox;
9 |
10 | public static class VisualHelper
11 | {
12 | internal static double GetUserY(this Visual? visual)
13 | {
14 | if (visual == null || !visual.IsVisible)
15 | return 0;
16 |
17 | Rect vRect = visual!.GetTransformedBounds()!.Value.Clip;
18 |
19 | return vRect.Y;
20 |
21 | }
22 |
23 |
24 | internal static Dictionary GetVisibleEditableParagraphs(this Visual? itemsControl) // where T : Visual
25 | {
26 | var items = new Dictionary();
27 |
28 | if (itemsControl == null || !itemsControl.IsVisible)
29 | return items;
30 |
31 | var edPars = itemsControl.GetVisualDescendants().OfType();
32 | foreach (var edpar in edPars)
33 | {
34 | Rect vRect = edpar.GetTransformedBounds()!.Value.Clip;
35 | //Rect vRect = edpar.Bounds;
36 |
37 | if (vRect.Y > 0) // Greater than zero means it's visible
38 | items.Add(edpar, edpar.GetTransformedBounds()!.Value.Clip);
39 | //items.Add(edpar, edpar.GetTransformedBounds()!.Value.Bounds);
40 | }
41 |
42 |
43 | return items;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/App.axaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Data.Core;
4 | using Avalonia.Data.Core.Plugins;
5 | using Avalonia.Markup.Xaml;
6 | using DemoApp_AvRichtextBox.ViewModels;
7 | using DemoApp_AvRichtextBox.Views;
8 | using System.Linq;
9 |
10 | namespace DemoApp_AvRichtextBox;
11 |
12 | public partial class App : Application
13 | {
14 | public override void Initialize()
15 | {
16 | AvaloniaXamlLoader.Load(this);
17 | }
18 |
19 | public override void OnFrameworkInitializationCompleted()
20 | {
21 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
22 | {
23 | // Avoid duplicate validations from both Avalonia and the CommunityToolkit.
24 | // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
25 | DisableAvaloniaDataAnnotationValidation();
26 | desktop.MainWindow = new MainWindow
27 | {
28 | DataContext = new MainWindowViewModel(),
29 | };
30 | }
31 |
32 | base.OnFrameworkInitializationCompleted();
33 | }
34 |
35 | private void DisableAvaloniaDataAnnotationValidation()
36 | {
37 | // Get an array of plugins to remove
38 | var dataValidationPluginsToRemove =
39 | BindingPlugins.DataValidators.OfType().ToArray();
40 |
41 | // remove each entry found
42 | foreach (var plugin in dataValidationPluginsToRemove)
43 | {
44 | BindingPlugins.DataValidators.Remove(plugin);
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/Assets/avalonia-logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cuikp/AvRichTextBox/145e48b7aecf1b896dc4a5fd573061b5bcd449d9/DemoApp_AvRichtextBox/Assets/avalonia-logo.ico
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/DemoApp_AvRichTextBox.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net8.0
5 | enable
6 | true
7 | app.manifest
8 |
9 |
10 |
11 | True
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | PreserveNewest
27 |
28 |
29 | PreserveNewest
30 |
31 |
32 | Never
33 |
34 |
35 | PreserveNewest
36 |
37 |
38 | PreserveNewest
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | None
51 | All
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/NumericSpinner/NumericSpinner.axaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
51 |
55 |
56 |
57 |
58 |
59 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/NumericSpinner/NumericSpinner.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Data;
4 | using Avalonia.Data.Converters;
5 | using Avalonia.Input;
6 | using Avalonia.Interactivity;
7 | using Avalonia.Media;
8 | using Avalonia.Threading;
9 | using AvRichTextBox;
10 | using System;
11 | using System.ComponentModel;
12 | using System.Globalization;
13 | using System.Runtime.CompilerServices;
14 |
15 | namespace CustomControls;
16 |
17 | public class NumericSpinnerViewModel
18 | {
19 |
20 | }
21 |
22 | public partial class NumericSpinner : UserControl
23 | {
24 | public delegate void ValueChangedHandler(double value);
25 | public event ValueChangedHandler? ValueChanged;
26 |
27 | public delegate void UserValueChangedHandler(double value);
28 | public event UserValueChangedHandler? UserValueChanged;
29 |
30 | public NumericSpinner()
31 | {
32 | InitializeComponent();
33 |
34 | this.DataContext = this;
35 |
36 | Step = 1;
37 |
38 | DownKeyTimer.Tick += DownKeyTimer_Tick;
39 | DownKeyTimer.Interval = new TimeSpan(0, 0, 0, 0, 200);
40 |
41 |
42 | CmdUp.AddHandler(InputElement.PointerPressedEvent, CmdUp_PointerPressed, RoutingStrategies.Tunnel);
43 | CmdUp.AddHandler(InputElement.PointerReleasedEvent, CmdUp_PointerReleased, RoutingStrategies.Tunnel);
44 | CmdDown.AddHandler(InputElement.PointerPressedEvent, CmdDown_PointerPressed, RoutingStrategies.Tunnel);
45 | CmdDown.AddHandler(InputElement.PointerReleasedEvent, CmdDown_PointerReleased, RoutingStrategies.Tunnel);
46 |
47 | Value = 20;
48 |
49 | }
50 |
51 | private double _Step = 1;
52 | public double Step { get { return _Step; } set { _Step = value; } }
53 |
54 | private void CmdUp_Click(object? sender, RoutedEventArgs e) { Value += (ControlIsDown ? Step * 10 : Step); UserValueChanged?.Invoke(Value); }
55 | private void CmdDown_Click(object? sender, RoutedEventArgs e) { Value -= (ControlIsDown ? Step * 10 : Step); UserValueChanged?.Invoke(Value); }
56 |
57 | internal DispatcherTimer DownKeyTimer = new();
58 |
59 | bool ControlIsDown = false;
60 | bool UpKeyDown = false;
61 | bool DownKeyDown = false;
62 |
63 | private void DownKeyTimer_Tick(object? sender, EventArgs e)
64 | {
65 | int fac = DownKeyDown ? -1 : 1;
66 | Value += (ControlIsDown ? Step * 10 : Step) * fac;
67 | UserValueChanged?.Invoke(Value);
68 | }
69 |
70 | private void CmdDown_PointerPressed(object? sender, PointerPressedEventArgs e)
71 | {
72 | DownKeyDown = true;
73 | ControlIsDown = e.KeyModifiers.HasFlag(KeyModifiers.Control);
74 | DownKeyTimer.Start();
75 | }
76 |
77 | private void CmdUp_PointerPressed(object? sender, PointerPressedEventArgs e)
78 | {
79 | UpKeyDown = true;
80 | ControlIsDown = e.KeyModifiers.HasFlag(KeyModifiers.Control);
81 | DownKeyTimer.Start();
82 | }
83 |
84 | private void CmdDown_PointerReleased(object? sender, PointerReleasedEventArgs e) { DownKeyDown = false; DownKeyTimer.Stop(); }
85 | private void CmdUp_PointerReleased(object? sender, PointerReleasedEventArgs e) { UpKeyDown = false; DownKeyTimer.Stop(); ; }
86 |
87 | public static readonly StyledProperty TextBackgroundProperty = AvaloniaProperty.Register(nameof(TextBackground));
88 |
89 | public SolidColorBrush TextBackground
90 | {
91 | get => (SolidColorBrush)GetValue(TextBackgroundProperty);
92 | set => SetValue(TextBackgroundProperty, value);
93 | }
94 |
95 | public static readonly StyledProperty TextPaddingProperty = AvaloniaProperty.Register(nameof(TextPadding));
96 |
97 | public Thickness TextPadding
98 | {
99 | get => (Thickness)GetValue(TextPaddingProperty);
100 | set => SetValue(TextPaddingProperty, value);
101 | }
102 |
103 | public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register(nameof(Value), 80, defaultBindingMode: BindingMode.TwoWay);
104 |
105 | public double Value
106 | {
107 | get => (double)GetValue(ValueProperty);
108 | set
109 | {
110 | if (value < MinValue) value = MinValue;
111 | if (value > MaxValue) value = MaxValue;
112 |
113 | if (Value != value)
114 | {
115 | SetValue(ValueProperty, value);
116 | ValueChanged?.Invoke(value);
117 | }
118 |
119 | }
120 | }
121 |
122 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
123 | {
124 | base.OnPropertyChanged(change);
125 |
126 | //if (change.Property.Name == nameof(Value))
127 | //{
128 | // double Val = (double)change.GetNewValue();
129 | // ValueChanged?.Invoke(Val);
130 | //}
131 |
132 | }
133 |
134 | public static readonly StyledProperty MaxValueProperty = AvaloniaProperty.Register(nameof(MaxValue));
135 |
136 | public double MaxValue
137 | {
138 | get { return (double)GetValue(MaxValueProperty); }
139 | set { SetValue(MaxValueProperty, value); }
140 | }
141 |
142 | private void MaxValuePropertyChanged(double Val) { MaxValue = Val; }
143 |
144 | public static readonly StyledProperty MinValueProperty = AvaloniaProperty.Register(nameof(MinValue));
145 |
146 | public double MinValue
147 | {
148 | get { return (double)GetValue(MinValueProperty); }
149 | set { SetValue(MinValueProperty, value); }
150 | }
151 |
152 | private void MinValuePropertyChanged(double Val) { MinValue = Val; }
153 |
154 |
155 | }
156 |
157 | public class DoubleToStringConverter : IValueConverter
158 | {
159 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
160 | {
161 | return value?.ToString() ?? "";
162 | }
163 |
164 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
165 | {
166 | if (double.TryParse(value as string, out double result))
167 | return result;
168 | return 0; // Default fallback
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using System;
3 |
4 | namespace DemoApp_AvRichtextBox;
5 |
6 | internal sealed class Program
7 | {
8 | // Initialization code. Don't use any Avalonia, third-party APIs or any
9 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
10 | // yet and stuff might break.
11 | [STAThread]
12 | public static void Main(string[] args) => BuildAvaloniaApp()
13 | .StartWithClassicDesktopLifetime(args);
14 |
15 | // Avalonia configuration, don't remove; also used by visual designer.
16 | public static AppBuilder BuildAvaloniaApp()
17 | => AppBuilder.Configure()
18 | .UsePlatformDetect()
19 | .WithInterFont()
20 | .LogToTrace();
21 | }
22 |
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/TestFiles/TestDocumentWord.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cuikp/AvRichTextBox/145e48b7aecf1b896dc4a5fd573061b5bcd449d9/DemoApp_AvRichtextBox/TestFiles/TestDocumentWord.docx
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/TestFiles/TestDocumentXamlPackage.xamlp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cuikp/AvRichTextBox/145e48b7aecf1b896dc4a5fd573061b5bcd449d9/DemoApp_AvRichtextBox/TestFiles/TestDocumentXamlPackage.xamlp
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/ViewLocator.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.Templates;
3 | using DemoApp_AvRichtextBox.ViewModels;
4 | using System;
5 |
6 | namespace DemoApp_AvRichtextBox;
7 |
8 | public class ViewLocator : IDataTemplate
9 | {
10 |
11 | public Control? Build(object? param)
12 | {
13 | if (param is null)
14 | return null;
15 |
16 | var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
17 | var type = Type.GetType(name);
18 |
19 | if (type != null)
20 | {
21 | return (Control)Activator.CreateInstance(type)!;
22 | }
23 |
24 | return new TextBlock { Text = "Not Found: " + name };
25 | }
26 |
27 | public bool Match(object? data)
28 | {
29 | return data is ViewModelBase;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/ViewModels/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 |
2 | using AvRichTextBox;
3 | using DocumentFormat.OpenXml.Spreadsheet;
4 | using System;
5 | using System.Collections.ObjectModel;
6 | using System.ComponentModel;
7 | using System.IO;
8 | using System.Runtime.CompilerServices;
9 |
10 | namespace DemoApp_AvRichtextBox.ViewModels;
11 |
12 | public partial class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged
13 | {
14 | public new event PropertyChangedEventHandler? PropertyChanged;
15 | public void NotifyPropertyChanged([CallerMemberName] String propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
16 |
17 |
18 | private string _CurrentOpenFilePath = Path.Combine(AppContext.BaseDirectory, "TestFiles", "NewFile");
19 | public string CurrentOpenFilePath { get => _CurrentOpenFilePath; set { _CurrentOpenFilePath = value; NotifyPropertyChanged(nameof(CurrentOpenFileNameExt)); } }
20 |
21 | internal string CurrentOpenFileName => Path.GetFileNameWithoutExtension(CurrentOpenFilePath);
22 | string CurrentExt => Path.GetExtension(CurrentOpenFilePath);
23 | public string CurrentOpenFileNameExt => "DemoApp_AvRichTextBox - " + CurrentOpenFileName + CurrentExt;
24 |
25 | public FlowDocument MyFlowDoc { get; set; } = new FlowDocument();
26 | //public ObservableCollection MyBlocks { get; set; } = [];
27 |
28 | public MainWindowViewModel()
29 | {
30 | //Testing creation a flow doc for binding:
31 | //Paragraph p = new();
32 | //p.Inlines.Add(new EditableRun("This is a fixxxxxrst inline"));
33 |
34 | //MyFlowDoc.Blocks.Clear();
35 | //MyFlowDoc.Blocks.Add(p);
36 |
37 | //MyBlocks.Add(p);
38 |
39 |
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 |
3 | namespace DemoApp_AvRichtextBox.ViewModels;
4 |
5 | public class ViewModelBase : ObservableObject
6 | {
7 | }
8 |
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Media;
4 | using AvRichTextBox;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Diagnostics;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Text.RegularExpressions;
11 |
12 | namespace DemoApp_AvRichtextBox.Views;
13 |
14 | public partial class MainWindow : Window
15 | {
16 |
17 | public static List GetAllFonts
18 | {
19 | get
20 | {
21 | List returnList = [];
22 | foreach (var font in FontManager.Current.SystemFonts)
23 | returnList.Add(font.Name);
24 | return returnList;
25 | }
26 |
27 | }
28 |
29 | public MainWindow()
30 | {
31 | InitializeComponent();
32 |
33 | Loaded += MainWindow_Loaded;
34 |
35 | FontsCB.ItemsSource = GetAllFonts;
36 |
37 | }
38 |
39 | private void MainWindow_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
40 | {
41 | MainRTB.FlowDocument.Selection_Changed += FlowDocument_Selection_Changed;
42 |
43 | progChange = false;
44 |
45 | ////Temp debugging
46 | //string testLocation = Path.Combine(AppContext.BaseDirectory, "TestFiles");
47 | //MainRTB.LoadWordDoc(Path.Combine(testLocation, "TestDocumentWord.docx"));
48 |
49 | }
50 |
51 | private void CreateNewDocumentMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
52 | {
53 | MainRTB.CloseDocument();
54 |
55 | }
56 |
57 | private void ShowPagePaddingValue()
58 | {
59 | PagePaddingNSL.Value = MainRTB.FlowDocument.PagePadding.Left;
60 | PagePaddingNSR.Value = MainRTB.FlowDocument.PagePadding.Right;
61 | PagePaddingNST.Value = MainRTB.FlowDocument.PagePadding.Top;
62 | PagePaddingNSB.Value = MainRTB.FlowDocument.PagePadding.Bottom;
63 | }
64 |
65 |
66 | private void FindTextBox_KeyDown(object? sender, Avalonia.Input.KeyEventArgs e)
67 | {
68 | FindTB.Background = Brushes.White;
69 |
70 | if (e.Key == Avalonia.Input.Key.Enter)
71 | {
72 | PerformFind();
73 | e.Handled = true;
74 | }
75 | }
76 |
77 | private void FindTextBox_GotFocus(object? sender, Avalonia.Input.GotFocusEventArgs e)
78 | {
79 | FindTB.Background = Brushes.White;
80 |
81 | }
82 |
83 | private void FindButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
84 | {
85 | PerformFind();
86 |
87 | }
88 |
89 | private void PerformFind()
90 | {
91 | FindTB.Background = Brushes.White;
92 |
93 | if (string.IsNullOrEmpty(FindTB.Text)) return;
94 |
95 | MatchCollection foundMatches = Regex.Matches(MainRTB.FlowDocument.Text, FindTB.Text);
96 | Match? firstMatch = foundMatches.FirstOrDefault(m => m.Index >= MainRTB.FlowDocument.Selection.End);
97 | if (firstMatch != null)
98 | {
99 | MainRTB.FlowDocument.Select(firstMatch.Index, FindTB.Text.Length);
100 | MainRTB.ScrollToSelection();
101 | }
102 | else
103 | {
104 | FindTB.Background = Brushes.Coral;
105 | FindBut.Focus();
106 | }
107 |
108 |
109 | }
110 |
111 | internal void PagePaddingNSL_ValueChanged(double value)
112 | {
113 | Thickness p = MainRTB.FlowDocument.PagePadding;
114 | MainRTB.FlowDocument.PagePadding = new Thickness(PagePaddingNSL.Value, p.Top, p.Right, p.Bottom);
115 | }
116 |
117 | internal void PagePaddingNST_ValueChanged(double value)
118 | {
119 | Thickness p = MainRTB.FlowDocument.PagePadding;
120 | MainRTB.FlowDocument.PagePadding = new Thickness(p.Left, PagePaddingNST.Value, p.Right, p.Bottom);
121 | }
122 |
123 | internal void PagePaddingNSR_ValueChanged(double value)
124 | {
125 | Thickness p = MainRTB.FlowDocument.PagePadding;
126 | MainRTB.FlowDocument.PagePadding = new Thickness(p.Left, p.Top, PagePaddingNSR.Value, p.Bottom);
127 | }
128 |
129 | internal void PagePaddingNSB_ValueChanged(double value)
130 | {
131 | Thickness p = MainRTB.FlowDocument.PagePadding;
132 | MainRTB.FlowDocument.PagePadding = new Thickness(p.Left, p.Top, p.Right, PagePaddingNSB.Value);
133 | }
134 |
135 |
136 | bool progChange = true;
137 |
138 | private void FlowDocument_Selection_Changed(TextRange selection)
139 | {
140 | FontSizeNS.Value = Math.Round((double)(selection.GetFormatting(FontSizeProperty) ?? 14D));
141 |
142 | object? selFFP = selection.GetFormatting(FontFamilyProperty);
143 | if (selFFP != null)
144 | {
145 | FontFamily ffamily = (FontFamily)selFFP;
146 | FontsCB.SelectedItem = ffamily.ToString();
147 | }
148 |
149 | Paragraph? selPar = selection.GetStartPar();
150 | if (selPar != null && !progChange)
151 | {
152 | progChange = true;
153 | LineSpacingNS.Value = selPar.LineSpacing;
154 | ParagraphBorderNS.Value = selPar.BorderThickness.Left;
155 | ParBorderCP.Color = selPar.BorderBrush.Color;
156 | ParBackgroundCP.Color = selPar.Background.Color;
157 | progChange = false;
158 | }
159 |
160 | }
161 |
162 | internal void FontSizeNS_UserValueChanged(double value)
163 | {
164 | MainRTB.FlowDocument.Selection.ApplyFormatting(FontSizeProperty, value);
165 |
166 | }
167 |
168 | internal void LineSpacingNS_UserValueChanged(double value)
169 | {
170 | foreach (Paragraph p in MainRTB.FlowDocument.GetSelectedParagraphs)
171 | p.LineSpacing = value;
172 | }
173 |
174 | internal void ParagraphBorderNS_UserValueChanged(double value)
175 | {
176 | foreach (Paragraph p in MainRTB.FlowDocument.GetSelectedParagraphs)
177 | p.BorderThickness = new Thickness(value);
178 |
179 | }
180 |
181 | private void ParBorder_ColorChanged(object? sender, ColorChangedEventArgs e)
182 | {
183 | if (progChange) return;
184 | SolidColorBrush hBrush = new(e.NewColor);
185 | foreach (Paragraph p in MainRTB.FlowDocument.GetSelectedParagraphs)
186 | p.BorderBrush = hBrush;
187 |
188 | }
189 |
190 | private void ParBackground_ColorChanged(object? sender, ColorChangedEventArgs e)
191 | {
192 | if (progChange) return;
193 | SolidColorBrush hBrush = new(e.NewColor);
194 | foreach (Paragraph p in MainRTB.FlowDocument.GetSelectedParagraphs)
195 | p.Background = hBrush;
196 |
197 | }
198 |
199 |
200 | private void FontCP_ColorChanged(object? sender, ColorChangedEventArgs e)
201 | {
202 | SolidColorBrush hBrush = new (e.NewColor);
203 | MainRTB.FlowDocument.Selection.ApplyFormatting(ForegroundProperty, hBrush);
204 | }
205 |
206 | private void HighlightCP_ColorChanged(object? sender, ColorChangedEventArgs e)
207 | {
208 | SolidColorBrush hBrush = new (e.NewColor);
209 | MainRTB.FlowDocument.Selection.ApplyFormatting(BackgroundProperty, hBrush);
210 | }
211 |
212 | private void DebugPanelCB_CheckedUnchecked(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
213 | {
214 | CheckBox? thisCB = sender as CheckBox;
215 | if (thisCB != null && MainRTB != null)
216 | //MainRTB.ToggleDebuggerPanel((bool)thisCB.IsChecked!);
217 | MainRTB.ShowDebuggerPanelInDebugMode = (bool)thisCB.IsChecked!;
218 | }
219 |
220 | private void FontsComboBox_DropDownClosed(object? sender, System.EventArgs e)
221 | {
222 | ComboBox? comboBox = sender as ComboBox;
223 | if (comboBox != null && comboBox.SelectedItem != null)
224 | {
225 | string? newFont = comboBox.SelectedItem.ToString();
226 | if (newFont != null)
227 | MainRTB.FlowDocument.Selection.ApplyFormatting(FontFamilyProperty, new FontFamily(newFont));
228 | }
229 |
230 | }
231 | }
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/Views/MainWindow_File.xaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Platform.Storage;
3 | using DemoApp_AvRichtextBox.ViewModels;
4 | using System;
5 | using System.IO;
6 |
7 | namespace DemoApp_AvRichtextBox.Views;
8 |
9 | public partial class MainWindow : Window
10 | {
11 | string openFilePath
12 | {
13 | get { return ((MainWindowViewModel)DataContext!).CurrentOpenFilePath; }
14 | set { ((MainWindowViewModel)DataContext!).CurrentOpenFilePath = value; }
15 | }
16 |
17 | string openFileName => ((MainWindowViewModel)DataContext!).CurrentOpenFileName;
18 |
19 | private async void LoadXamlPackageMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
20 | {
21 |
22 | FilePickerOpenOptions filePickerOptions = new()
23 | {
24 | Title = "Open Xaml package file",
25 | SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Path.Combine(AppContext.BaseDirectory, "TestFiles")),
26 | FileTypeFilter = [new("Xaml package files") { Patterns = ["*.xamlp"] }],
27 | AllowMultiple = false
28 | };
29 |
30 | var topLevel = TopLevel.GetTopLevel(this);
31 | var files = await topLevel!.StorageProvider.OpenFilePickerAsync(filePickerOptions);
32 |
33 | if (files.Count == 1)
34 | {
35 | string? f = files[0].TryGetLocalPath();
36 | if (f != null)
37 | {
38 | openFilePath = f;
39 | MainRTB.LoadXamlPackage(f);
40 | ShowPagePaddingValue();
41 | }
42 |
43 |
44 | }
45 | }
46 |
47 |
48 | private void SaveXamlPackageMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
49 | {
50 | MainRTB.SaveXamlPackage(Path.Combine(Path.GetDirectoryName(openFilePath)!, openFileName + ".xamlp"));
51 | }
52 |
53 | private async void LoadRtfFileMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
54 | {
55 | FilePickerOpenOptions filePickerOptions = new()
56 | {
57 | Title = "Open Rtf file",
58 | SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Path.Combine(AppContext.BaseDirectory, "TestFiles")),
59 | FileTypeFilter = [new("Rtf files") { Patterns = ["*.rtf"] }],
60 | AllowMultiple = false
61 | };
62 |
63 | var topLevel = TopLevel.GetTopLevel(this);
64 | var files = await topLevel!.StorageProvider.OpenFilePickerAsync(filePickerOptions);
65 |
66 | if (files.Count == 1)
67 | {
68 | string? f = files[0].TryGetLocalPath();
69 | if (f != null)
70 | {
71 | openFilePath = f;
72 | MainRTB.LoadRtfDoc(f);
73 | ShowPagePaddingValue();
74 | }
75 | }
76 | }
77 |
78 | private void SaveRtfFileMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
79 | {
80 | MainRTB.SaveRtfDoc(Path.Combine(Path.GetDirectoryName(openFilePath)!, openFileName + ".rtf"));
81 | }
82 |
83 |
84 | private async void LoadWordFileMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
85 | {
86 |
87 | FilePickerOpenOptions filePickerOptions = new()
88 | {
89 | Title = "Open Word doc file",
90 | SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Path.Combine(AppContext.BaseDirectory, "TestFiles")),
91 | FileTypeFilter = [new("Word doc files") { Patterns = ["*.docx"], }],
92 | AllowMultiple = false
93 | };
94 |
95 |
96 | var topLevel = TopLevel.GetTopLevel(this);
97 | var files = await topLevel!.StorageProvider.OpenFilePickerAsync(filePickerOptions);
98 |
99 | if (files.Count == 1)
100 | {
101 | string? f = files[0].TryGetLocalPath();
102 | if (f != null)
103 | {
104 | openFilePath = f;
105 | MainRTB.LoadWordDoc(f);
106 | ShowPagePaddingValue();
107 | }
108 |
109 | }
110 |
111 | }
112 |
113 | private void SaveWordFileMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
114 | {
115 | MainRTB.SaveWordDoc(Path.Combine(Path.GetDirectoryName(openFilePath)!, openFileName + ".docx"));
116 | }
117 |
118 | private void SaveHtmlFileMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
119 | {
120 | MainRTB.SaveHtmlDoc(Path.Combine(Path.GetDirectoryName(openFilePath)!, openFileName + ".html"));
121 | }
122 |
123 | private async void LoadHtmlFileMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
124 | {
125 | FilePickerOpenOptions filePickerOptions = new()
126 | {
127 | Title = "Open Html file",
128 | SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Path.Combine(AppContext.BaseDirectory, "TestFiles")),
129 | FileTypeFilter = [new("Html files") { Patterns = ["*.html"], }],
130 | AllowMultiple = false
131 | };
132 |
133 | var topLevel = TopLevel.GetTopLevel(this);
134 | var files = await topLevel!.StorageProvider.OpenFilePickerAsync(filePickerOptions);
135 |
136 | if (files.Count == 1)
137 | {
138 | string? f = files[0].TryGetLocalPath();
139 | if (f != null)
140 | {
141 | openFilePath = f;
142 | MainRTB.LoadHtmlDoc(f);
143 | ShowPagePaddingValue();
144 | }
145 |
146 | }
147 |
148 | }
149 |
150 | }
--------------------------------------------------------------------------------
/DemoApp_AvRichtextBox/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | enable
4 | 11.0.2
5 |
6 |
7 |
--------------------------------------------------------------------------------