Repository: whistyun/Markdown.Avalonia
Branch: master
Commit: d71a34d8de3c
Files: 422
Total size: 1.3 MB
Directory structure:
gitextract_t8vp1hh_/
├── .gitattributes
├── .github/
│ └── workflows/
│ └── build.yml
├── .gitignore
├── ColorDocument.Avalonia/
│ ├── ClassNames.cs
│ ├── ColorDocument.Avalonia.csproj
│ ├── DocumentElement.cs
│ ├── DocumentElements/
│ │ ├── BlockquoteElement.cs
│ │ ├── CTextBlockElement.cs
│ │ ├── DocumentRootElement.cs
│ │ ├── HeaderElement.cs
│ │ ├── ListBlockElement.cs
│ │ ├── ListItemElement.cs
│ │ ├── NoteBlockElement.cs
│ │ ├── PlainCodeBlockElement.cs
│ │ ├── TableBlockElement.cs
│ │ ├── TableCellElement.cs
│ │ ├── TextBlockElement.cs
│ │ ├── TextMarkerStyle.cs
│ │ └── UnBlockElement.cs
│ ├── EnumerableExt.cs
│ ├── NumberToOrder.cs
│ ├── RegionUtil.cs
│ ├── SelectionList.cs
│ └── SelectionUtil.cs
├── ColorTextBlock.Avalonia/
│ ├── CBold.cs
│ ├── CCode.cs
│ ├── CHyperlink.cs
│ ├── CImage.cs
│ ├── CInline.cs
│ ├── CInlineUIContainer.cs
│ ├── CItalic.cs
│ ├── CLineBreak.cs
│ ├── CRun.cs
│ ├── CSpan.cs
│ ├── CStrikethrough.cs
│ ├── CTextBlock.cs
│ ├── CTextBlockAutomationPeer.cs
│ ├── CUnderline.cs
│ ├── ColorTextBlock.Avalonia.csproj
│ ├── Fonts/
│ │ └── FontFamilyCollector.cs
│ ├── Geometies/
│ │ ├── CGeometry.cs
│ │ ├── DecoratorGeometry.cs
│ │ ├── DummyGeometryForControl.cs
│ │ ├── ImageGeometry.cs
│ │ ├── LineBreakMarkGeometry.cs
│ │ ├── TextGeometry.cs
│ │ └── TextLineGeometry.cs
│ ├── StringToRunConverter.cs
│ ├── TextPointer.cs
│ └── TextVerticalAlignment.cs
├── LICENSE.txt
├── Markdown.Avalonia/
│ ├── Markdown.Avalonia.csproj
│ ├── MarkdownScrollViewer.cs
│ ├── MdAvPlugins.cs
│ └── Properties/
│ └── AssemblyInfo.cs
├── Markdown.Avalonia.Html/
│ ├── Core/
│ │ ├── Parsers/
│ │ │ ├── ButtonParser.cs
│ │ │ ├── CodeBlockParser.cs
│ │ │ ├── CommentParser.cs
│ │ │ ├── DetailsParser.cs
│ │ │ ├── HorizontalRuleParser.cs
│ │ │ ├── ITagParser.cs
│ │ │ ├── ImageParser.cs
│ │ │ ├── InputParser.cs
│ │ │ ├── OrderListParser.cs
│ │ │ ├── ProgressParser.cs
│ │ │ ├── TagIgnoreParser.cs
│ │ │ ├── TextAreaParser.cs
│ │ │ ├── TextNodeParser.cs
│ │ │ ├── TypicalBlockParser.cs
│ │ │ ├── TypicalBlockParser.tsv
│ │ │ ├── TypicalInlineParser.cs
│ │ │ ├── TypicalInlineParser.tsv
│ │ │ ├── TypicalParseInfo.cs
│ │ │ └── UnorderListParser.cs
│ │ ├── Parsers.MarkdigExtensions/
│ │ │ ├── FigureParser.cs
│ │ │ └── GridTableParser.cs
│ │ ├── ReplaceManager.cs
│ │ ├── Tags.cs
│ │ ├── TextRange.cs
│ │ ├── UnknownTagException.cs
│ │ ├── UnknownTagsOption.cs
│ │ └── Utils/
│ │ ├── DocUtils.cs
│ │ ├── EnumerableExt.cs
│ │ ├── HtmlUtils.cs
│ │ ├── Length.cs
│ │ ├── NodeCollectionExt.cs
│ │ └── StringExt.cs
│ ├── HtmlBlockParser.cs
│ ├── HtmlInlineParser.cs
│ ├── HtmlPlugin.cs
│ ├── Markdown.Avalonia.Html.csproj
│ ├── SimpleHtmlUtils.cs
│ └── Tables/
│ ├── AutoScaleColumnDefinitions.cs
│ ├── Table.cs
│ └── TableCell.cs
├── Markdown.Avalonia.Svg/
│ ├── Markdown.Avalonia.Svg.csproj
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── SvgFormat.cs
│ ├── SvgImageResolver.cs
│ └── VectorImage.cs
├── Markdown.Avalonia.SyntaxHigh/
│ ├── Alias.cs
│ ├── CodeBlockElement.cs
│ ├── CodePad.cs
│ ├── Extensions/
│ │ ├── HSV.cs
│ │ ├── HighlightWrapper.cs
│ │ ├── MixHighlightingBrush.cs
│ │ └── SyntaxHighlightWrapperExtension.cs
│ ├── Markdown.Avalonia.SyntaxHigh.csproj
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── StyleCollections/
│ │ ├── AppendixOfDefaultTheme.axaml
│ │ ├── AppendixOfDefaultTheme.axaml.cs
│ │ ├── AppendixOfFluentAvalonia.axaml
│ │ ├── AppendixOfFluentAvalonia.axaml.cs
│ │ ├── AppendixOfFluentTheme.axaml
│ │ └── AppendixOfFluentTheme.axaml.cs
│ ├── StyleEdit.cs
│ ├── SyntaxHighlightProvider.cs
│ ├── SyntaxHiglight.cs
│ ├── SyntaxOverride.cs
│ └── ThemeDetector.cs
├── Markdown.Avalonia.Tight/
│ ├── CascadeDictionary.cs
│ ├── ChatAISetup.cs
│ ├── ContainerSwitcher.cs
│ ├── Controls/
│ │ ├── AutoScaleColumnDefinitions.cs
│ │ └── Rule.cs
│ ├── CopyMode.cs
│ ├── EmojiTable.cs
│ ├── EmojiTable.txt
│ ├── EngineUpg.cs
│ ├── Extensions/
│ │ ├── AlphaExtension.cs
│ │ ├── ComplementaryExtension.cs
│ │ ├── DivideColorExtension.cs
│ │ └── MultiplyExtension.cs
│ ├── Header.cs
│ ├── HeaderScrolled.cs
│ ├── HeaderScrolledEventArgs.cs
│ ├── IMarkdownEngine.cs
│ ├── IMarkdownEngine2.cs
│ ├── IMarkdownEngineBase.cs
│ ├── Markdown.Avalonia.Tight.csproj
│ ├── Markdown.cs
│ ├── MarkdownScrollViewer.cs
│ ├── MarkdownStyle.cs
│ ├── MdAvPlugins.cs
│ ├── Parsers/
│ │ ├── BlockParser.cs
│ │ ├── BlockParser2.cs
│ │ ├── BlockParserExt.cs
│ │ ├── Builtin/
│ │ │ ├── AbstractHeaderParser.cs
│ │ │ ├── AbstractHorizontalParser.cs
│ │ │ ├── AbstractListParser.cs
│ │ │ ├── AtxHeaderParser.cs
│ │ │ ├── BlockquotesParser.cs
│ │ │ ├── CommonHorizontalParser.cs
│ │ │ ├── CommonListParser.cs
│ │ │ ├── ContainerBlockParser.cs
│ │ │ ├── ExtHorizontalParser.cs
│ │ │ ├── ExtListParser.cs
│ │ │ ├── FencedCodeBlockParser.cs
│ │ │ ├── IndentCodeBlockParser.cs
│ │ │ ├── NoteParser.cs
│ │ │ ├── SetextHeaderParser.cs
│ │ │ └── TableParser.cs
│ │ ├── InlineParser.cs
│ │ └── ParseStatus.cs
│ ├── Plugins/
│ │ ├── BlockOverride2.cs
│ │ ├── IBlockOverrider.cs
│ │ ├── IMdAvPlugin.cs
│ │ ├── IStyleEditor.cs
│ │ └── SetupInfo.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── StyleCollections/
│ │ ├── INamedStyle.cs
│ │ ├── MarkdownStyleDefaultTheme.axaml
│ │ ├── MarkdownStyleDefaultTheme.axaml.cs
│ │ ├── MarkdownStyleFluentAvalonia.axaml
│ │ ├── MarkdownStyleFluentAvalonia.axaml.cs
│ │ ├── MarkdownStyleFluentTheme.axaml
│ │ ├── MarkdownStyleFluentTheme.axaml.cs
│ │ ├── MarkdownStyleGithubLike.axaml
│ │ ├── MarkdownStyleGithubLike.axaml.cs
│ │ ├── MarkdownStyleStandard.axaml
│ │ └── MarkdownStyleStandard.axaml.cs
│ ├── Tables/
│ │ ├── ITable.cs
│ │ ├── ITableCell.cs
│ │ ├── TextileTable.cs
│ │ └── TextileTableCell.cs
│ ├── TextMarkerStyle.cs
│ └── Utils/
│ ├── DefaultBitmapLoader.cs
│ ├── DefaultHyperlinkCommand.cs
│ ├── DefaultPathResolver.cs
│ ├── Helper.cs
│ ├── IBitmapLoader.cs
│ ├── IContainerBlockHandler.cs
│ ├── IImageResolver.cs
│ ├── IPathResolver.cs
│ ├── InterassemblyUtil.cs
│ ├── TextUtil.cs
│ └── ThemeDetector.cs
├── Markdown.Avalonia.props
├── Markdown.Avalonia.sln
├── Packages.ps1
├── README.md
├── benchmark/
│ └── MdAvBench/
│ ├── Apps/
│ │ ├── App.axaml
│ │ └── App.axaml.cs
│ ├── BenchmarkOfCTextBlock.cs
│ ├── MdAvBench.csproj
│ ├── Program.cs
│ ├── StringLogger.cs
│ └── Xamls/
│ ├── CTextBlockData.axaml
│ ├── CTextBlockData.axaml.cs
│ ├── TextBlockData.axaml
│ └── TextBlockData.axaml.cs
├── demos/
│ ├── Markdown.AvaloniaDemo/
│ │ ├── App.axaml
│ │ ├── App.axaml.cs
│ │ ├── Assets/
│ │ │ ├── AppendingStyles.axaml
│ │ │ ├── Pegasus-Mode.xshd
│ │ │ └── XamlTemplate.txt
│ │ ├── DynamicStyleBehavior.cs
│ │ ├── MainWindow.md
│ │ ├── Markdown.AvaloniaDemo.csproj
│ │ ├── MyConverter.cs
│ │ ├── Program.cs
│ │ ├── ViewLocator.cs
│ │ ├── ViewModels/
│ │ │ ├── MainWindowViewModel.cs
│ │ │ └── ViewModelBase.cs
│ │ ├── Views/
│ │ │ ├── MainWindow.axaml
│ │ │ └── MainWindow.axaml.cs
│ │ └── nuget.config
│ ├── Markdown.AvaloniaFluentAvaloniaDemo/
│ │ ├── App.axaml
│ │ ├── App.axaml.cs
│ │ ├── Assets/
│ │ │ ├── AppendingStyles.axaml
│ │ │ └── XamlTemplate.txt
│ │ ├── Assets2/
│ │ │ └── MainWindow.md
│ │ ├── DynamicStyleBehavior.cs
│ │ ├── MainWindow.md
│ │ ├── Markdown.AvaloniaFluentAvaloniaDemo.csproj
│ │ ├── MyConverter.cs
│ │ ├── Program.cs
│ │ ├── ViewLocator.cs
│ │ ├── ViewModels/
│ │ │ ├── MainWindowViewModel.cs
│ │ │ └── ViewModelBase.cs
│ │ ├── Views/
│ │ │ ├── MainWindow.axaml
│ │ │ └── MainWindow.axaml.cs
│ │ └── nuget.config
│ └── Markdown.AvaloniaFluentDemo/
│ ├── App.axaml
│ ├── App.axaml.cs
│ ├── Assets/
│ │ ├── AppendingStyles.axaml
│ │ └── XamlTemplate.txt
│ ├── Assets2/
│ │ └── MainWindow.md
│ ├── DynamicStyleBehavior.cs
│ ├── MainWindow.md
│ ├── Markdown.AvaloniaFluentDemo.csproj
│ ├── MyConverter.cs
│ ├── Program.cs
│ ├── ViewLocator.cs
│ ├── ViewModels/
│ │ ├── MainWindowViewModel.cs
│ │ └── ViewModelBase.cs
│ ├── Views/
│ │ ├── MainWindow.axaml
│ │ └── MainWindow.axaml.cs
│ └── nuget.config
├── example/
│ ├── CustomStyle/
│ │ ├── CustomStyle/
│ │ │ ├── App.axaml
│ │ │ ├── App.axaml.cs
│ │ │ ├── CustomStyle.csproj
│ │ │ ├── MainWindow.axaml
│ │ │ ├── MainWindow.axaml.cs
│ │ │ ├── Program.cs
│ │ │ ├── SetStyles.axaml
│ │ │ ├── SetStyles.axaml.cs
│ │ │ ├── UseEmbeddedStyle.axaml
│ │ │ ├── UseEmbeddedStyle.axaml.cs
│ │ │ └── nuget.config
│ │ └── CustomStyle.sln
│ └── HowToUse/
│ ├── HowToUse/
│ │ ├── App.axaml
│ │ ├── App.axaml.cs
│ │ ├── HowToUse.csproj
│ │ ├── MainWindow.axaml
│ │ ├── MainWindow.axaml.cs
│ │ ├── Program.cs
│ │ ├── UseBinding.axaml
│ │ ├── UseBinding.axaml.cs
│ │ ├── UseBindingViewModel.cs
│ │ ├── WriteMarkdownInXaml.axaml
│ │ ├── WriteMarkdownInXaml.axaml.cs
│ │ └── nuget.config
│ └── HowToUse.sln
├── key.snk
├── pack_readme/
│ ├── Markdown.Avalonia.Html.md
│ ├── Markdown.Avalonia.Svg.md
│ ├── Markdown.Avalonia.SyntaxHigh.md
│ ├── Markdown.Avalonia.Tight.md
│ └── Markdown.Avalonia.md
├── packs/
│ ├── ColorTextBlock.Avalonia.11.0.0-d2.nupkg
│ ├── Markdown.Avalonia.11.0.0-d2.nupkg
│ ├── Markdown.Avalonia.Html.11.0.0-d2.nupkg
│ ├── Markdown.Avalonia.Svg.11.0.0-d2.nupkg
│ ├── Markdown.Avalonia.SyntaxHigh.11.0.0-d2.nupkg
│ └── Markdown.Avalonia.Tight.11.0.0-d2.nupkg
└── tests/
├── UnitTest.Base/
│ ├── Apps/
│ │ ├── App.axaml
│ │ └── App.axaml.cs
│ ├── ChangeOutputPathNamer.cs
│ ├── UnitTest.Base.csproj
│ ├── UnitTestBase.cs
│ └── Utils/
│ ├── BrokenXamlWriter.cs
│ ├── RunOnUIAttribute.cs
│ ├── TextUtil.cs
│ └── Util.cs
├── UnitTest.CTxt/
│ ├── .gitignore
│ ├── Assets/
│ │ └── Fonts/
│ │ └── license.html
│ ├── Texts/
│ │ └── MainWindow.md
│ ├── UnitTest.CTxt.csproj
│ ├── UnitTestCTxt.cs
│ ├── Utils/
│ │ ├── ApprovalImageWriter.cs
│ │ └── ImageFileApprover.cs
│ └── Xamls/
│ ├── Test1.axaml
│ ├── Test1.axaml.cs
│ ├── Test2.axaml
│ ├── Test2.axaml.cs
│ ├── Test3.axaml
│ ├── Test3.axaml.cs
│ ├── Test4.axaml
│ ├── Test4.axaml.cs
│ ├── Test5.axaml
│ ├── Test5.axaml.cs
│ ├── Test6.axaml
│ ├── Test6.axaml.cs
│ ├── Test7.axaml
│ ├── Test7.axaml.cs
│ ├── Test99.axaml
│ └── Test99.axaml.cs
├── UnitTest.Md/
│ ├── .gitignore
│ ├── Out/
│ │ ├── UnitTestMd.Transform_givenBlockqoute_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenCodes_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenContainer_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenEmoji.approved.txt
│ │ ├── UnitTestMd.Transform_givenHorizontalRules_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenImages_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenLinksInline_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenLists1_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenLists2_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenLists3_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenMixing2_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenMixing_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenTables1_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMd.Transform_givenTest1_generatesExpectedResult.approved.txt
│ │ └── UnitTestMd.Transform_givenTextStyles_generatesExpectedResult.approved.txt
│ ├── Texts/
│ │ ├── Blockquite.md
│ │ ├── Codes.md
│ │ ├── ContainerBlock.md
│ │ ├── Emoji.md
│ │ ├── HorizontalRules.md
│ │ ├── Images.md
│ │ ├── Links_inline_style.md
│ │ ├── Lists1.md
│ │ ├── Lists2.md
│ │ ├── Lists3.md
│ │ ├── Mixing.md
│ │ ├── Mixing2.md
│ │ ├── Tables.md
│ │ ├── Test1.md
│ │ └── Text_style.md
│ ├── UnitTest.Md.csproj
│ └── UnitTestMd.cs
├── UnitTest.MdHtml/
│ ├── .gitignore
│ ├── Out/
│ │ ├── UnitTest.Button.approved.txt
│ │ ├── UnitTest.CodeBlock.approved.txt
│ │ ├── UnitTest.Details.approved.txt
│ │ ├── UnitTest.InlineCode.approved.txt
│ │ ├── UnitTest.Input.approved.txt
│ │ ├── UnitTest.List.approved.txt
│ │ ├── UnitTest.Mixing.approved.txt
│ │ ├── UnitTest.Progres.approved.txt
│ │ ├── UnitTest.Table.approved.txt
│ │ ├── UnitTest.TypicalBlock.approved.txt
│ │ └── UnitTest.TypicalInline.approved.txt
│ ├── Texts/
│ │ ├── Button.html
│ │ ├── CodeBlock.html
│ │ ├── Details.html
│ │ ├── InlineCode.html
│ │ ├── Input.html
│ │ ├── List.html
│ │ ├── Mixing.html
│ │ ├── Progres.html
│ │ ├── Table.html
│ │ ├── TypicalBlock.html
│ │ └── TypicalInline.html
│ ├── UnitTest.MdHtml.csproj
│ ├── UnitTest.cs
│ └── Utils.cs
├── UnitTest.MdSyntax/
│ ├── .gitignore
│ ├── Out/
│ │ ├── UnitTestMdSyntax.Transform_givenBlockqoute_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenCodes_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenContainer_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenEmoji.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenHorizontalRules_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenImages_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenLinksInline_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenLists1_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenLists2_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenLists3_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenMixing2_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenMixing_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenTables1_generatesExpectedResult.approved.txt
│ │ ├── UnitTestMdSyntax.Transform_givenTest1_generatesExpectedResult.approved.txt
│ │ └── UnitTestMdSyntax.Transform_givenTextStyles_generatesExpectedResult.approved.txt
│ ├── Texts/
│ │ ├── Blockquite.md
│ │ ├── Codes.md
│ │ ├── ContainerBlock.md
│ │ ├── Emoji.md
│ │ ├── HorizontalRules.md
│ │ ├── Images.md
│ │ ├── Links_inline_style.md
│ │ ├── Lists1.md
│ │ ├── Lists2.md
│ │ ├── Lists3.md
│ │ ├── Mixing.md
│ │ ├── Mixing2.md
│ │ ├── Tables.md
│ │ ├── Test1.md
│ │ └── Text_style.md
│ ├── UnitTest.MdSyntax.csproj
│ ├── UnitTestConverter.cs
│ └── UnitTestMdSyntax.cs
└── UnitTest.props
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
================================================
FILE: .github/workflows/build.yml
================================================
name: .NET
on:
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
versions: [ 12.0.0, 12.0.1 ]
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
- name: Clean
run: dotnet clean
env:
AVA_VER: ${{ matrix.versions }}
- name: Restore
run: dotnet restore
env:
AVA_VER: ${{ matrix.versions }}
- name: Build
run: dotnet build --no-restore
env:
AVA_VER: ${{ matrix.versions }}
- name: Test
run: dotnet test --no-build --verbosity normal
env:
AVA_VER: ${{ matrix.versions }}
================================================
FILE: .gitignore
================================================
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
.vs
packages
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
.builds
*.dotCover
## TODO: If you have NuGet Package Restore enabled, uncomment this
#packages/
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# ReSharper is a .NET coding add-in
_ReSharper*
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Others
[Bb]in
[Oo]bj
sql
TestResults
*.Cache
ClientBin
stylecop.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
############
## Windows
############
# Windows image file caches
Thumbs.db
# Folder config file
Desktop.ini
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
# Mac crap
.DS_Store
================================================
FILE: ColorDocument.Avalonia/ClassNames.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ColorDocument.Avalonia
{
public static class ClassNames
{
public const string Heading1Class = "Heading1";
public const string Heading2Class = "Heading2";
public const string Heading3Class = "Heading3";
public const string Heading4Class = "Heading4";
public const string Heading5Class = "Heading5";
public const string Heading6Class = "Heading6";
public const string CodeBlockClass = "CodeBlock";
public const string ContainerBlockClass = "ContainerBlock";
public const string NoContainerClass = "NoContainer";
public const string BlockquoteClass = "Blockquote";
public const string NoteClass = "Note";
public const string ParagraphClass = "Paragraph";
public const string TableClass = "Table";
public const string TableHeaderClass = "TableHeader";
public const string TableFirstRowClass = "FirstTableRow";
public const string TableRowOddClass = "OddTableRow";
public const string TableRowEvenClass = "EvenTableRow";
public const string TableLastRowClass = "LastTableRow";
public const string TableFooterClass = "TableFooter";
public const string ListClass = "List";
public const string ListMarkerClass = "ListMarker";
}
}
================================================
FILE: ColorDocument.Avalonia/ColorDocument.Avalonia.csproj
================================================
$(PackageTargetFrameworks)
$(PackageVersion)
Bevan Arps(original); whistyun
ColorDocument.Avalonia
$(PackageVersion)
whistyun
Copyright (c) 2024 whistyun
https://github.com/whistyun/Markdown.Avalonia/tree/master/ColorDocument.Avalonia/
9
MIT
enable
================================================
FILE: ColorDocument.Avalonia/DocumentElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using System.Collections.Generic;
using System.Text;
namespace ColorDocument.Avalonia
{
public abstract class DocumentElement
{
private ISelectionRenderHelper? _helper;
public abstract Control Control { get; }
public abstract IEnumerable Children { get; }
public ISelectionRenderHelper? Helper
{
get => _helper;
set
{
_helper = value;
foreach (var child in Children)
child.Helper = value;
}
}
public Rect GetRect(Layoutable anchor) => Control.GetRectInDoc(anchor).GetValueOrDefault();
public abstract void Select(Point from, Point to);
public abstract void UnSelect();
public virtual string GetSelectedText()
{
var builder = new StringBuilder();
ConstructSelectedText(builder);
return builder.ToString();
}
public abstract void ConstructSelectedText(StringBuilder stringBuilder);
}
public interface ISelectionRenderHelper
{
void Register(Control control);
void Unregister(Control control);
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/BlockquoteElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
///
/// The document element for expression of blockquote.
///
// 引用を表現するためのドキュメント要素
public class BlockquoteElement : DocumentElement
{
private Lazy _block;
private EnumerableEx _children;
private SelectionList? _prevSelection;
public override Control Control => _block.Value;
public override IEnumerable Children => _children;
public BlockquoteElement(IEnumerable child)
{
_block = new Lazy(Create);
_children = child.ToEnumerable();
}
private Border Create()
{
var panel = new StackPanel();
panel.Orientation = Orientation.Vertical;
panel.Classes.Add(ClassNames.BlockquoteClass);
foreach (var child in Children)
panel.Children.Add(child.Control);
var border = new Border();
border.Classes.Add(ClassNames.BlockquoteClass);
border.Child = panel;
return border;
}
public override void Select(Point from, Point to)
{
var selection = SelectionUtil.SelectVertical(Control, _children, from, to);
if (_prevSelection is not null)
{
foreach (var ps in _prevSelection)
{
if (!selection.Any(cs => ReferenceEquals(cs, ps)))
{
ps.UnSelect();
}
}
}
_prevSelection = selection;
}
public override void UnSelect()
{
foreach (var child in _children)
child.UnSelect();
}
public override void ConstructSelectedText(StringBuilder builder)
{
if (_prevSelection is null)
return;
var preLen = builder.Length;
foreach (var para in _prevSelection)
{
para.ConstructSelectedText(builder);
if (preLen == builder.Length)
continue;
if (builder[builder.Length - 1] != '\n')
builder.Append('\n');
}
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/CTextBlockElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using ColorTextBlock.Avalonia;
using System;
using System.Collections.Generic;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
public class CTextBlockElement : DocumentElement
{
private Lazy _text;
public string Text => _text.Value.Text;
public override Control Control => _text.Value;
public override IEnumerable Children => Array.Empty();
public CTextBlockElement(IEnumerable inlines)
{
_text = new Lazy(() =>
{
var text = new CTextBlock();
foreach (var inline in inlines)
text.Content.Add(inline);
return text;
});
}
public CTextBlockElement(IEnumerable inlines, string appendClass)
{
_text = new Lazy(() =>
{
var text = new CTextBlock();
foreach (var inline in inlines)
text.Content.Add(inline);
text.Classes.Add(appendClass);
return text;
});
}
public CTextBlockElement(IEnumerable inlines, string appendClass, TextAlignment alignment)
{
_text = new Lazy(() =>
{
var text = new CTextBlock();
foreach (var inline in inlines)
text.Content.Add(inline);
text.TextAlignment = alignment;
text.Classes.Add(appendClass);
return text;
});
}
public override void Select(Point from, Point to)
{
var text = _text.Value;
var fromPoint = text.CalcuatePointerFrom(from.X, from.Y);
var toPoint = text.CalcuatePointerFrom(to.X, to.Y);
text.Select(fromPoint, toPoint);
}
public override void UnSelect()
{
_text.Value.ClearSelection();
}
public override void ConstructSelectedText(StringBuilder builder)
{
builder.Append(_text.Value.GetSelectedText());
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/DocumentRootElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
///
/// The top document element.
///
public class DocumentRootElement : DocumentElement
{
private Lazy _block;
private EnumerableEx _children;
private SelectionList? _prevSelection;
public override Control Control => _block.Value;
public override IEnumerable Children => _children;
public DocumentRootElement(IEnumerable child)
{
_block = new Lazy(Create);
_children = child.ToEnumerable();
}
private StackPanel Create()
{
var panel = new StackPanel();
panel.Orientation = Orientation.Vertical;
foreach (var child in _children)
panel.Children.Add(child.Control);
return panel;
}
public override void Select(Point from, Point to)
{
var selection = SelectionUtil.SelectVertical(Control, _children, from, to);
if (_prevSelection is not null)
{
foreach (var ps in _prevSelection)
{
if (!selection.Any(cs => ReferenceEquals(cs, ps)))
{
ps.UnSelect();
}
}
}
_prevSelection = selection;
}
public override void UnSelect()
{
foreach (var child in _children)
child.UnSelect();
}
public override void ConstructSelectedText(StringBuilder builder)
{
if (_prevSelection is null)
return;
var preLen = builder.Length;
foreach (var para in _prevSelection)
{
para.ConstructSelectedText(builder);
if (preLen == builder.Length)
continue;
if (builder[builder.Length - 1] != '\n')
builder.Append('\n');
}
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/HeaderElement.cs
================================================
using ColorTextBlock.Avalonia;
using System.Collections.Generic;
namespace ColorDocument.Avalonia.DocumentElements
{
public class HeaderElement : CTextBlockElement
{
public int Level { get; }
public HeaderElement(IEnumerable inlines, int level) :
base(inlines, level switch
{
1 => ClassNames.Heading1Class,
2 => ClassNames.Heading2Class,
3 => ClassNames.Heading3Class,
4 => ClassNames.Heading4Class,
5 => ClassNames.Heading5Class,
_ => ClassNames.Heading6Class,
})
{
Level = level switch
{
1 => 1,
2 => 2,
3 => 3,
4 => 4,
5 => 5,
_ => 6,
};
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/ListBlockElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using ColorTextBlock.Avalonia;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
public class ListBlockElement : DocumentElement
{
private Lazy _control;
private EnumerableEx _items;
private SelectionList? _prevSelection;
public override Control Control => _control.Value;
public override IEnumerable Children => _items;
public ListBlockElement(TextMarkerStyle marker, IEnumerable items)
{
_control = new Lazy(() => CreateList(marker));
_items = items.ToEnumerable();
}
public override void Select(Point from, Point to)
{
var selection = SelectionUtil.SelectVertical(Control, _items, from, to);
if (_prevSelection is not null)
{
foreach (var ps in _prevSelection)
{
if (!selection.Any(cs => ReferenceEquals(cs, ps)))
{
ps.UnSelect();
}
}
}
_prevSelection = selection;
}
public override void UnSelect()
{
foreach (var c in _items)
c.UnSelect();
}
private Grid CreateList(TextMarkerStyle marker)
{
var grid = new Grid();
grid.Classes.Add(ClassNames.ListClass);
grid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
grid.ColumnDefinitions.Add(new ColumnDefinition());
int index = 0;
foreach (var item in _items)
{
var markerTxt = new CTextBlock(marker.CreateMakerText(index));
var itemCtrl = item.Control;
item.MarkerText = markerTxt.Text;
// adjust baseline
if (FindFirstFrom(itemCtrl) is { } controlTxt)
markerTxt.ObserveBaseHeightOf(controlTxt);
grid.RowDefinitions.Add(new RowDefinition());
markerTxt.TextAlignment = TextAlignment.Right;
markerTxt.TextWrapping = TextWrapping.NoWrap;
markerTxt.Classes.Add(ClassNames.ListMarkerClass);
Grid.SetRow(markerTxt, index);
Grid.SetColumn(markerTxt, 0);
grid.Children.Add(markerTxt);
Grid.SetRow(itemCtrl, index);
Grid.SetColumn(itemCtrl, 1);
grid.Children.Add(itemCtrl);
++index;
}
return grid;
static CTextBlock? FindFirstFrom(Control ctrl)
{
if (ctrl is Panel pnl)
{
foreach (var chld in pnl.Children)
{
var res = FindFirstFrom(chld);
if (res != null) return res;
}
}
if (ctrl is CTextBlock ctxt)
{
return ctxt;
}
return null;
}
}
public override void ConstructSelectedText(StringBuilder builder)
{
if (_prevSelection is null)
return;
foreach (var para in _prevSelection.Cast())
{
builder.Append(para.MarkerText).Append(' ');
var listElmTxt = para.GetSelectedText().Replace("\r\n", "\n").Replace('\r', '\n');
builder.Append(listElmTxt);
if (!listElmTxt.EndsWith("\n"))
builder.Append('\n');
}
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/ListItemElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
public class ListItemElement : DocumentElement
{
private Lazy _panel;
private EnumerableEx _elements;
private SelectionList? _prevSelection;
internal string MarkerText { get; set; }
public override Control Control => _panel.Value;
public override IEnumerable Children => _elements;
public ListItemElement(IEnumerable contents)
{
_elements = contents.ToEnumerable();
_panel = new Lazy(() =>
{
var panel = new StackPanel();
foreach (var content in _elements)
panel.Children.Add(content.Control);
return panel;
});
}
public override void Select(Point from, Point to)
{
var selection = SelectionUtil.SelectVertical(Control, _elements, from, to);
if (_prevSelection is not null)
{
foreach (var ps in _prevSelection)
{
if (!selection.Any(cs => ReferenceEquals(cs, ps)))
{
ps.UnSelect();
}
}
}
_prevSelection = selection;
}
public override void UnSelect()
{
foreach (var c in _elements)
c.UnSelect();
}
public override void ConstructSelectedText(StringBuilder builder)
{
if (_prevSelection is null)
return;
var preLen = builder.Length;
foreach (var para in _prevSelection)
{
para.ConstructSelectedText(builder);
if (preLen == builder.Length)
continue;
if (builder[builder.Length - 1] != '\n')
builder.Append('\n');
}
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/NoteBlockElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using ColorTextBlock.Avalonia;
using System;
using System.Collections.Generic;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
public class NoteBlockElement : DocumentElement
{
private CTextBlockElement _child;
private TextAlignment? _indiAlignment;
private Lazy _block;
public override Control Control => _block.Value;
public override IEnumerable Children => new[] { _child };
public NoteBlockElement(IEnumerable content, TextAlignment? indiAlignment)
{
_child = new CTextBlockElement(content);
_indiAlignment = indiAlignment;
_block = new Lazy(Create);
}
private Border Create()
{
var note = (CTextBlock)_child.Control;
note.Classes.Add(ClassNames.NoteClass);
if (_indiAlignment.HasValue)
{
note.TextAlignment = _indiAlignment.Value;
}
var result = new Border();
result.Classes.Add(ClassNames.NoteClass);
result.Child = note;
return result;
}
public override void Select(Point from, Point to)
=> _child.Select(from, to);
public override void UnSelect()
{
_child.UnSelect();
}
public override void ConstructSelectedText(StringBuilder builder)
{
_child.ConstructSelectedText(builder);
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/PlainCodeBlockElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using ColorTextBlock.Avalonia;
using System;
using System.Collections.Generic;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
public class PlainCodeBlockElement : DocumentElement
{
private string _code;
private Lazy _border;
public override Control Control => _border.Value;
public override IEnumerable Children => Array.Empty();
public PlainCodeBlockElement(string code)
{
_code = code;
_border = new Lazy(CreateBlock);
}
public override void Select(Point from, Point to)
{
}
public override void UnSelect()
{
}
public Border CreateBlock()
{
var ctxt = new TextBlock()
{
Text = _code,
TextWrapping = TextWrapping.NoWrap
};
ctxt.Classes.Add(ClassNames.CodeBlockClass);
var scrl = new ScrollViewer();
scrl.Classes.Add(ClassNames.CodeBlockClass);
scrl.Content = ctxt;
scrl.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
var result = new Border();
result.Classes.Add(ClassNames.CodeBlockClass);
result.Child = scrl;
return result;
}
public override void ConstructSelectedText(StringBuilder stringBuilder)
{
stringBuilder.Append(_code);
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/TableBlockElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
public class TableBlockElement : DocumentElement
{
private Lazy _table;
private TableCellElement[][] _head;
private TableCellElement[][] _body;
private TableCellElement[][] _foot;
private bool _autoAdjust;
private EnumerableEx _all;
private SelectionList? _prevSelection;
public override Control Control => _table.Value;
public override IEnumerable Children => _all;
public TableBlockElement(
IEnumerable> thead,
IEnumerable> tbody,
IEnumerable> tfoot,
bool autoAdjust) :
this(
thead.Select(ln => ln.ToArray()).ToArray(),
tbody.Select(ln => ln.ToArray()).ToArray(),
tfoot.Select(ln => ln.ToArray()).ToArray(),
autoAdjust)
{ }
public TableBlockElement(
TableCellElement[][] thead,
TableCellElement[][] tbody,
TableCellElement[][] tfoot,
bool autoAdjust)
{
_head = thead;
_body = tbody;
_foot = tfoot;
_all = _head.SelectMany(l => l)
.Concat(_body.SelectMany(l => l))
.Concat(_foot.SelectMany(l => l))
.ToEnumerable();
_autoAdjust = autoAdjust;
_table = new Lazy(CreateTable);
}
public override void Select(Point from, Point to)
{
var selection = SelectionUtil.SelectGrid(Control, _all, from, to);
if (_prevSelection is not null)
{
foreach (var ps in _prevSelection)
{
if (!selection.Any(cs => ReferenceEquals(cs, ps)))
{
ps.UnSelect();
}
}
}
_prevSelection = selection;
}
public override void UnSelect()
{
foreach (var child in _all)
child.UnSelect();
}
private Border CreateTable()
{
var rowInfs = new List();
CreateRows(rowInfs, _head, ClassNames.TableHeaderClass);
CreateRows(rowInfs, _body);
CreateRows(rowInfs, _foot, ClassNames.TableFooterClass);
int maxColCnt = rowInfs.Max(r => r.ColumnCount);
if (_autoAdjust)
{
for (int i = 0; i < rowInfs.Count; ++i)
{
var rowInf = rowInfs[i];
while (rowInf.ColumnCount < maxColCnt)
{
var cellCtrl = new Border();
Grid.SetRow(cellCtrl, i);
Grid.SetColumn(cellCtrl, rowInf.ColumnCount);
rowInf.Cells.Add(cellCtrl);
rowInf.ColumnCount++;
}
}
}
var grid = new Grid();
grid.Classes.Add(ClassNames.TableClass);
grid.RowDefinitions.AddRange(Enumerable.Range(0, rowInfs.Count).Select(_ => new RowDefinition()));
grid.ColumnDefinitions.AddRange(Enumerable.Range(0, maxColCnt).Select(_ => new ColumnDefinition()));
foreach (var rowInf in rowInfs)
{
foreach (var cell in rowInf.Cells)
cell.Classes.AddRange(rowInf.Classes);
grid.Children.AddRange(rowInf.Cells);
}
var border = new Border();
border.Classes.Add(ClassNames.TableClass);
border.Child = grid;
//var grid = new Grid();
//grid.Classes.Add(ClassNames.TableClass);
//var border = new Border();
//border.Classes.Add(ClassNames.TableClass);
//border.Child = grid;
//int rowOffset = 0;
//
//int hRowOffset = rowOffset;
//List hInfs = SetupRow(grid, _head, ref rowOffset, ClassNames.TableHeaderClass);
//int bRowOffset = rowOffset;
//List bInfs = SetupRow(grid, _body, ref rowOffset);
//int fRowOffset = rowOffset;
//List fInfs = SetupRow(grid, _foot, ref rowOffset, ClassNames.TableFooterClass);
//
//int colCnt = hInfs.Concat(bInfs).Concat(fInfs).Max(i => i.ColumnCount);
//
//if (_autoAdjust)
//{
// AdjustRow(grid, hInfs, hRowOffset, colCnt);
// AdjustRow(grid, bInfs, bRowOffset, colCnt);
// AdjustRow(grid, fInfs, fRowOffset, colCnt);
//}
//
//foreach (var _ in Enumerable.Range(0, colCnt))
//{
// grid.ColumnDefinitions.Add(new ColumnDefinition());
//}
return border;
}
private static List SetupRow(
Grid grid,
TableCellElement[][] rows,
ref int gridRowIdx,
string? classNm = null)
{
// The list of multi-row cells.
// Key: Column index where the target cell is located.
var multiRowsAtColIdx = new Dictionary();
var rowInfs = new List();
var maxColCount = 0;
int startRowInSection = gridRowIdx;
for (var i = 0; i < rows.Length; ++gridRowIdx)
{
var row = rows[i];
grid.RowDefinitions.Add(new RowDefinition());
// Set up classes for cell in this row.
string[] classes;
if (classNm is not null)
classes = new[] { classNm };
else
{
var rowIdxInSection = gridRowIdx - startRowInSection;
if (rowIdxInSection == 0)
{
if (i == rows.Length - 1)
classes = new[] { ClassNames.TableRowOddClass, ClassNames.TableFirstRowClass, ClassNames.TableLastRowClass };
else
classes = new[] { ClassNames.TableRowOddClass, ClassNames.TableFirstRowClass };
}
else
{
var oddOrEven = rowIdxInSection % 2 == 0 ? ClassNames.TableRowOddClass : ClassNames.TableRowEvenClass;
if (i == rows.Length - 1)
classes = new[] { oddOrEven, ClassNames.TableLastRowClass };
else
classes = new[] { oddOrEven };
}
}
var rowspansColOffset = multiRowsAtColIdx.Sum(e => e.Value.ColSpan);
/*
* In this row, is space exists to insert cell?
*
* eg. has space
* __________________________________
* | 2x1 cell | 1x1 cell | 1x1 cell |
* -> | |‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾|
* ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
*
* eg. has no space: multi-rows occupy all space in this row.
* __________________________________
* | 2x1 cell | 2x2 cell |
* -> | | |
* ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
*
*/
if (rowspansColOffset == 0 || rowspansColOffset < maxColCount)
{
int colIdx = 0;
foreach (var cell in row)
{
while (multiRowsAtColIdx.TryGetValue(colIdx, out var span))
{
colIdx += span.ColSpan;
}
var cellCtrl = cell.Control;
cell.Row = gridRowIdx;
cell.Column = colIdx;
Grid.SetRow(cellCtrl, gridRowIdx);
Grid.SetColumn(cellCtrl, colIdx);
if (cell.RowSpan > 1) Grid.SetRowSpan(cellCtrl, cell.RowSpan);
if (cell.ColSpan > 1) Grid.SetColumnSpan(cellCtrl, cell.ColSpan);
cellCtrl.Classes.AddRange(classes);
grid.Children.Add(cellCtrl);
if (cell.RowSpan > 1)
{
multiRowsAtColIdx[colIdx] =
new MdSpan(cell.RowSpan, cell.ColSpan);
}
colIdx += cell.ColSpan;
}
rowInfs.Add(new RowInfo(classes, colIdx));
if (maxColCount < colIdx) maxColCount = colIdx;
++i;
}
else
{
rowInfs.Add(new RowInfo(classes, rowspansColOffset));
}
// Removes multi-row cells, 複数行にまたがるセルの削除(必要なら)
foreach (var spanEntry in multiRowsAtColIdx.ToArray())
{
if (--spanEntry.Value.Life == 0)
{
multiRowsAtColIdx.Remove(spanEntry.Key);
}
}
}
// if any multirow is left, insert an empty row.
while (multiRowsAtColIdx.Count > 0)
{
grid.RowDefinitions.Add(new RowDefinition());
var colOffset = 0;
foreach (var spanEntry in multiRowsAtColIdx.OrderBy(tpl => tpl.Key))
{
while (colOffset < spanEntry.Key)
{
var cellCtrl = new Border();
Grid.SetRow(cellCtrl, gridRowIdx);
Grid.SetColumn(cellCtrl, colOffset);
grid.Children.Add(cellCtrl);
colOffset++;
}
colOffset += spanEntry.Value.ColSpan;
if (--spanEntry.Value.Life == 0)
{
multiRowsAtColIdx.Remove(spanEntry.Key);
}
}
rowInfs.Add(new RowInfo(Array.Empty(), colOffset));
gridRowIdx++;
}
return rowInfs;
}
private static void AdjustRow(Grid grid, List rowInfs, int rowOffset, int colCnt)
{
for (var rowIdx = 0; rowIdx < rowInfs.Count; ++rowIdx)
{
var rowInf = rowInfs[rowIdx];
for (var colIdx = rowInf.ColumnCount; colIdx < colCnt; ++colIdx)
{
var cellCtrl = new Border();
Grid.SetRow(cellCtrl, rowIdx + rowOffset);
Grid.SetColumn(cellCtrl, colIdx);
cellCtrl.Classes.AddRange(rowInf.Classes);
grid.Children.Insert(SearchInsPos(grid.Children, cellCtrl), cellCtrl);
}
}
int SearchInsPos(IList list, Control tgt)
{
int min = 0, max = list.Count;
var tgtRow = Grid.GetRow(tgt);
var tgtCol = Grid.GetColumn(tgt);
int mid = 0;
while (min < max)
{
mid = (min + max) / 2;
var ctrl = list[mid];
var ctrlRow = Grid.GetRow(ctrl);
var ctrlCol = Grid.GetColumn(ctrl);
if (tgtRow < ctrlRow || (tgtRow == ctrlRow && tgtCol < ctrlCol))
{
max = mid - 1;
}
else if (tgtRow > ctrlRow || (tgtRow == ctrlRow && tgtCol > ctrlCol))
{
min = mid + 1;
}
else break;
}
for (var i = Math.Min(Math.Min(Math.Min(min, max), mid), list.Count); i < list.Count; ++i)
{
var ctrl = list[i];
var ctrlRow = Grid.GetRow(ctrl);
var ctrlCol = Grid.GetColumn(ctrl);
if (tgtRow < ctrlRow || (tgtRow == ctrlRow && tgtCol < ctrlCol))
{
return i;
}
}
return list.Count;
}
}
private void CreateRows(List rowInfs, TableCellElement[][] rows, string? classNm = null)
{
// The list of multi-row cells.
// Key: Column index where the target cell is located.
var multiRowsAtColIdx = new Dictionary();
var maxColCount = 0;
int detailsRowIdx = 0;
for (int i = 0; i < rows.Length;)
{
var rinf = new RowInf();
SetupClass(rinf, detailsRowIdx++, classNm);
var rowspansColOffset = multiRowsAtColIdx.Sum(e => e.Value.ColSpan);
/*
* In this row, is space exists to insert cell?
*
* eg. has space
* __________________________________
* | 2x1 cell | 1x1 cell | 1x1 cell |
* -> | |‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾|
* ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
*
* eg. has no space: multi-rows occupy all space in this row.
* __________________________________
* | 2x1 cell | 2x2 cell |
* -> | | |
* ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
*
*/
if (rowspansColOffset == 0 || rowspansColOffset < maxColCount)
{
int colIdx = 0;
foreach (var cell in rows[i])
{
while (multiRowsAtColIdx.TryGetValue(colIdx, out var span))
{
colIdx += span.ColSpan;
}
var cellCtrl = cell.Control;
cell.Row = rowInfs.Count;
cell.Column = colIdx;
Grid.SetRow(cellCtrl, rowInfs.Count);
Grid.SetColumn(cellCtrl, colIdx);
if (cell.RowSpan > 1) Grid.SetRowSpan(cellCtrl, cell.RowSpan);
if (cell.ColSpan > 1) Grid.SetColumnSpan(cellCtrl, cell.ColSpan);
rinf.Cells.Add(cellCtrl);
if (cell.RowSpan > 1)
{
multiRowsAtColIdx[colIdx] =
new MdSpan(cell.RowSpan, cell.ColSpan);
}
colIdx += cell.ColSpan;
}
foreach (var left in multiRowsAtColIdx.Where(tpl => tpl.Key >= colIdx)
.OrderBy(tpl => tpl.Key))
{
while (colIdx < left.Key)
{
var cellCtrl = new Border();
Grid.SetRow(cellCtrl, rowInfs.Count);
Grid.SetColumn(cellCtrl, colIdx);
rinf.Cells.Add(cellCtrl);
++colIdx;
}
colIdx += left.Value.ColSpan;
}
rinf.ColumnCount = colIdx;
if (maxColCount < colIdx) maxColCount = colIdx;
++i;
}
else
{
rinf.ColumnCount = rowspansColOffset;
}
rowInfs.Add(rinf);
// Removes multi-row cells, 複数行にまたがるセルの削除(必要なら)
foreach (var spanEntry in multiRowsAtColIdx.ToArray())
{
if (--spanEntry.Value.Life == 0)
{
multiRowsAtColIdx.Remove(spanEntry.Key);
}
}
}
// if any multirow is left, insert an empty row.
while (multiRowsAtColIdx.Count > 0)
{
var rinf = new RowInf();
SetupClass(rinf, detailsRowIdx++, classNm);
var colIdx = 0;
foreach (var spanEntry in multiRowsAtColIdx.OrderBy(tpl => tpl.Key))
{
while (colIdx < spanEntry.Key)
{
var cellCtrl = new Border();
Grid.SetRow(cellCtrl, rowInfs.Count);
Grid.SetColumn(cellCtrl, colIdx);
rinf.Cells.Add(cellCtrl);
colIdx++;
}
colIdx += spanEntry.Value.ColSpan;
if (--spanEntry.Value.Life == 0)
{
multiRowsAtColIdx.Remove(spanEntry.Key);
}
}
rinf.ColumnCount = colIdx;
rowInfs.Add(rinf);
}
if (classNm is null)
{
rowInfs.Last().Classes.Add(ClassNames.TableLastRowClass);
}
static void SetupClass(RowInf rinf, int rowIndex, string? classNm)
{
if (classNm is not null)
rinf.Classes.Add(classNm);
else if (rowIndex == 0)
rinf.Classes.AddRange(new[] { ClassNames.TableRowOddClass, ClassNames.TableFirstRowClass });
else if (rowIndex % 2 == 0)
rinf.Classes.Add(ClassNames.TableRowOddClass);
else
rinf.Classes.Add(ClassNames.TableRowEvenClass);
}
}
public override void ConstructSelectedText(StringBuilder builder)
{
if (_prevSelection is null)
return;
string[,] cellTxt = new string[
_all.Max(c => c.Row + c.RowSpan),
_all.Max(c => c.Column + c.ColSpan)
];
foreach (var para in _prevSelection.Cast())
{
cellTxt[para.Row, para.Column] = para.GetSelectedText().TrimEnd().Replace("\r\n", "\r").Replace('\n', '\r');
}
for (int i = 0; i < cellTxt.GetLength(0); i++)
{
var preLen = builder.Length;
for (int j = 0; j < cellTxt.GetLength(1); j++)
{
builder.Append(cellTxt[i, j] ?? "");
builder.Append("\t");
}
if (builder.Length - preLen == 0)
continue;
if (builder[builder.Length - 1] != '\n')
builder.Append('\n');
}
}
class MdSpan
{
public int Life { get; set; }
public int ColSpan { get; }
public MdSpan(int l, int c)
{
Life = l;
ColSpan = c;
}
}
class RowInf
{
public List Classes { get; } = new List(5);
public List Cells { get; } = new List();
public int ColumnCount;
}
class RowInfo
{
public string[] Classes { get; }
public int ColumnCount { get; }
public RowInfo(string[] classes, int colCount)
{
Classes = classes;
ColumnCount = colCount;
}
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/TableCellElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
public class TableCellElement : DocumentElement
{
private readonly Lazy _control;
private readonly EnumerableEx _items;
private SelectionList? _prevSelection;
internal int Row { get; set; }
internal int Column { get; set; }
public int RowSpan { set; get; }
public int ColSpan { set; get; }
public TextAlignment? Horizontal { set; get; }
public VerticalAlignment? Vertical { set; get; }
public override Control Control => _control.Value;
public override IEnumerable Children => _items;
public TableCellElement(DocumentElement cell)
{
_items = new[] { cell }.ToEnumerable();
_control = new Lazy(CreateCell);
}
public TableCellElement(IEnumerable cells)
{
_items = cells.ToEnumerable();
_control = new Lazy(CreateCell);
}
public override void Select(Point from, Point to)
{
var selection = SelectionUtil.SelectVertical(Control, _items, from, to);
if (_prevSelection is not null)
{
foreach (var ps in _prevSelection)
{
if (!selection.Any(cs => ReferenceEquals(cs, ps)))
{
ps.UnSelect();
}
}
}
_prevSelection = selection;
}
public override void UnSelect()
{
foreach (var child in _items)
child.UnSelect();
}
private Border CreateCell()
{
if (_items.Count == 1)
{
return new Border() { Child = Setup(_items[0].Control) };
}
else
{
var pnl = new StackPanel() { Orientation = Orientation.Vertical };
foreach (var cnt in _items)
pnl.Children.Add(Setup(cnt.Control));
return new Border() { Child = pnl };
}
}
private Control Setup(Control control)
{
if (Horizontal.HasValue)
{
control.SetCurrentValue(TextBlock.TextAlignmentProperty, Horizontal.Value);
switch (Horizontal.Value)
{
case TextAlignment.Left:
control.HorizontalAlignment = HorizontalAlignment.Left;
break;
case TextAlignment.Right:
control.HorizontalAlignment = HorizontalAlignment.Right;
break;
case TextAlignment.Center:
control.HorizontalAlignment = HorizontalAlignment.Center;
break;
}
}
return control;
}
public override void ConstructSelectedText(StringBuilder builder)
{
if (_prevSelection is null)
return;
var preLen = builder.Length;
foreach (var para in _prevSelection)
{
para.ConstructSelectedText(builder);
if (preLen == builder.Length)
continue;
if (builder[builder.Length - 1] != '\n')
builder.Append('\n');
}
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/TextBlockElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Media;
using System;
using System.Collections.Generic;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
public class TextBlockElement : DocumentElement
{
private Lazy _text;
public string? Text => _text.Value.Text;
public override Control Control => _text.Value;
public override IEnumerable Children => Array.Empty();
public TextBlockElement(IEnumerable inlines)
{
_text = new Lazy(() =>
{
var text = new TextBlock();
if (text.Inlines is null)
{
text.Inlines = new InlineCollection();
}
text.Inlines.AddRange(inlines);
return text;
});
}
public TextBlockElement(IEnumerable inlines, string appendClass)
{
_text = new Lazy(() =>
{
var text = new TextBlock();
if (text.Inlines is null)
{
text.Inlines = new InlineCollection();
}
text.Inlines.AddRange(inlines);
text.Classes.Add(appendClass);
return text;
});
}
public TextBlockElement(IEnumerable inlines, string appendClass, TextAlignment alignment)
{
_text = new Lazy(() =>
{
var text = new TextBlock();
if (text.Inlines is null)
{
text.Inlines = new InlineCollection();
}
text.Inlines.AddRange(inlines);
text.TextAlignment = alignment;
text.Classes.Add(appendClass);
return text;
});
}
public override void Select(Point from, Point to) { }
public override void UnSelect() { }
public override void ConstructSelectedText(StringBuilder stringBuilder)
{
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/TextMarkerStyle.cs
================================================
using Markdown.Avalonia;
using System;
namespace ColorDocument.Avalonia.DocumentElements
{
public enum TextMarkerStyle
{
Box,
Circle,
Decimal,
Disc,
LowerLatin,
LowerRoman,
UpperLatin,
UpperRoman,
Square,
}
public static class MarkdownStyleExt
{
public static string CreateMakerText(this TextMarkerStyle textMarker, int index)
{
switch (textMarker)
{
default:
throw new InvalidOperationException("sorry library manager forget to modify about listmerker.");
case TextMarkerStyle.Disc:
return "•";
case TextMarkerStyle.Box:
return "▪";
case TextMarkerStyle.Circle:
return "○";
case TextMarkerStyle.Square:
return "❏";
case TextMarkerStyle.Decimal:
return (index + 1).ToString() + ".";
case TextMarkerStyle.LowerLatin:
return NumberToOrder.ToLatin(index + 1).ToLower() + ".";
case TextMarkerStyle.UpperLatin:
return NumberToOrder.ToLatin(index + 1) + ".";
case TextMarkerStyle.LowerRoman:
return NumberToOrder.ToRoman(index + 1).ToLower() + ".";
case TextMarkerStyle.UpperRoman:
return NumberToOrder.ToRoman(index + 1) + ".";
}
}
}
}
================================================
FILE: ColorDocument.Avalonia/DocumentElements/UnBlockElement.cs
================================================
using Avalonia;
using Avalonia.Controls;
using System;
using System.Collections.Generic;
using System.Text;
namespace ColorDocument.Avalonia.DocumentElements
{
public class UnBlockElement : DocumentElement
{
private Control _control;
public override Control Control => _control;
public override IEnumerable Children => Array.Empty();
public UnBlockElement(Control control)
{
_control = control;
}
public override void Select(Point from, Point to) { }
public override void UnSelect() { }
public override void ConstructSelectedText(StringBuilder stringBuilder)
{
}
}
}
================================================
FILE: ColorDocument.Avalonia/EnumerableExt.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ColorDocument.Avalonia
{
internal static class EnumerableExt
{
public static EnumerableEx ToEnumerable(this IEnumerable enumerable)
{
if (enumerable is List list)
return new EnumerableExLst(list);
else if (enumerable is T[] array)
return new EnumerableExAry(array);
return new EnumerableExLzy(enumerable);
}
}
internal abstract class EnumerableEx : IEnumerable
{
public abstract int Count { get; }
public abstract T this[int idx] { get; }
public abstract IEnumerator GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
internal class EnumerableExLzy : EnumerableEx
{
private Lazy _lzy;
public EnumerableExLzy(IEnumerable enm)
{
_lzy = new Lazy(() => enm.ToArray());
}
public override int Count => _lzy.Value.Length;
public override T this[int idx] { get => _lzy.Value[idx]; }
public override IEnumerator GetEnumerator() => ((ICollection)_lzy.Value).GetEnumerator();
}
internal class EnumerableExAry : EnumerableEx
{
private T[] _array;
public EnumerableExAry(T[] array)
{
_array = array;
}
public override int Count => _array.Length;
public override T this[int idx] { get => _array[idx]; }
public override IEnumerator GetEnumerator() => ((ICollection)_array).GetEnumerator();
}
internal class EnumerableExLst : EnumerableEx
{
private IList _list;
public EnumerableExLst(IList array)
{
_list = array;
}
public override int Count => _list.Count;
public override T this[int idx] { get => _list[idx]; }
public override IEnumerator GetEnumerator() => _list.GetEnumerator();
}
}
================================================
FILE: ColorDocument.Avalonia/NumberToOrder.cs
================================================
using System;
using System.Collections.Generic;
using System.Text;
namespace Markdown.Avalonia
{
static class NumberToOrder
{
public static string ToRoman(int number)
{
// roman can treat between 1 and 3999
if (number < 0 || number >= 4000) return number.ToString();
if (number >= 1000) return "M" + ToRoman(number - 1000);
if (number >= 900) return "CM" + ToRoman(number - 900);
if (number >= 500) return "D" + ToRoman(number - 500);
if (number >= 400) return "CD" + ToRoman(number - 400);
if (number >= 100) return "C" + ToRoman(number - 100);
if (number >= 90) return "XC" + ToRoman(number - 90);
if (number >= 50) return "L" + ToRoman(number - 50);
if (number >= 40) return "XL" + ToRoman(number - 40);
if (number >= 10) return "X" + ToRoman(number - 10);
if (number >= 9) return "IX" + ToRoman(number - 9);
if (number >= 5) return "V" + ToRoman(number - 5);
if (number >= 4) return "IV" + ToRoman(number - 4);
if (number >= 1) return "I" + ToRoman(number - 1);
if (number == 0) return "";
throw new ArgumentOutOfRangeException("something bad happened");
}
public static string ToLatin(int number)
{
var buff = new StringBuilder();
while (number > 0)
{
var mod = (number - 1) % 26;
buff.Insert(0, (char)(mod + 'A'));
number = (number - mod) / 26;
}
return buff.ToString();
}
}
}
================================================
FILE: ColorDocument.Avalonia/RegionUtil.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
namespace ColorDocument.Avalonia
{
internal static class RegionUtil
{
public static Rect? GetRectInDoc(this Control control, Layoutable anchor)
{
if (!LayoutInformation.GetPreviousArrangeBounds(control).HasValue)
return null;
double driftX = 0;
double driftY = 0;
StyledElement? c;
for (c = control.Parent;
c is not null
&& c is Layoutable layoutable
&& !ReferenceEquals(anchor, layoutable);
c = c.Parent)
{
driftX += layoutable.Bounds.X;
driftY += layoutable.Bounds.Y;
}
return new Rect(
control.Bounds.X + driftX,
control.Bounds.Y + driftY,
control.Bounds.Width,
control.Bounds.Height);
}
public static EnumerableEx GetRectInDoc(this EnumerableEx controls, Layoutable anchor)
where T : DocumentElement
{
var rs = new DocumentElementWithBound[controls.Count];
for (var i = 0; i < rs.Length; ++i)
{
var doc = controls[i];
var rect = doc.Control.GetRectInDoc(anchor);
if (rect.HasValue)
{
rs[i] = new DocumentElementWithBound(doc, rect.Value);
}
}
return new EnumerableExAry(rs);
}
}
internal struct DocumentElementWithBound
{
public DocumentElement Element { get; }
public Rect Rect { get; }
public DocumentElementWithBound(DocumentElement c, Rect r)
{
Element = c;
Rect = r;
}
}
}
================================================
FILE: ColorDocument.Avalonia/SelectionList.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ColorDocument.Avalonia
{
public class SelectionList : IList
{
private SelectDirection _direction;
private SelectRange _range;
private IList _elements;
public SelectionList(SelectDirection direction, SelectRange range, IList elements)
{
_direction = direction;
_range = range;
_elements = elements;
}
public SelectDirection Direction => _direction;
public DocumentElement this[int index]
{
get => _elements[index];
set => throw new InvalidOperationException();
}
public int Count => _elements.Count;
public bool IsReadOnly => true;
public void Add(DocumentElement item) => throw new InvalidOperationException();
public void Clear() => throw new InvalidOperationException();
public bool Contains(DocumentElement item) => _elements.Contains(item);
public void CopyTo(DocumentElement[] array, int arrayIndex) => _elements.CopyTo(array, arrayIndex);
public IEnumerator GetEnumerator() => _elements.GetEnumerator();
public int IndexOf(DocumentElement item) => _elements.IndexOf(item);
public void Insert(int index, DocumentElement item) => throw new InvalidOperationException();
public bool Remove(DocumentElement item) => throw new InvalidOperationException();
public void RemoveAt(int index) => throw new InvalidOperationException();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public enum SelectDirection
{
Forward, Backward
}
public enum SelectRange
{
Part = 0b0001,
Begin = 0b0011,
End = 0b0101,
Fill = 0b0111,
}
}
================================================
FILE: ColorDocument.Avalonia/SelectionUtil.cs
================================================
using Avalonia;
using Avalonia.Layout;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ColorDocument.Avalonia
{
internal static class SelectionUtil
{
public static SelectionList SelectVertical(Layoutable anchor, EnumerableEx elements, Point from, Point to)
where T : DocumentElement
{
var c = elements.GetRectInDoc(anchor);
int fp = ComputeIdxVertical(c, from);
int tp = ComputeIdxVertical(c, to);
var list = Select(c, from, to, fp, tp);
SelectRange rng =
(Math.Min(fp, tp) == 0 ? SelectRange.Begin : SelectRange.Part)
| (Math.Max(fp, tp) == elements.Count - 1 ? SelectRange.End : SelectRange.Part);
if (fp < tp)
{
return new SelectionList(SelectDirection.Forward, rng, list);
}
else
{
list.Reverse();
return new SelectionList(SelectDirection.Backward, rng, list);
}
}
public static SelectionList SelectGrid(Layoutable anchor, EnumerableEx elements, Point from, Point to)
where T : DocumentElement
{
var c = elements.GetRectInDoc(anchor);
int fp = ComputeIdxGrid(c, from);
int tp = ComputeIdxGrid(c, to);
var list = Select(c, from, to, fp, tp);
SelectRange rng =
(Math.Min(fp, tp) == 0 ? SelectRange.Begin : SelectRange.Part)
| (Math.Max(fp, tp) == elements.Count - 1 ? SelectRange.End : SelectRange.Part);
if (fp < tp)
{
return new SelectionList(SelectDirection.Forward, rng, list);
}
else
{
list.Reverse();
return new SelectionList(SelectDirection.Backward, rng, list);
}
}
static int ComputeIdxVertical(EnumerableEx elements, Point pnt)
{
if (pnt.X <= 0 && pnt.Y <= 0)
return 0;
if (pnt.X == Double.PositiveInfinity && pnt.Y == Double.PositiveInfinity)
return elements.Count - 1;
foreach ((var c, int i) in elements.Select((value, index) => (value, index)))
{
var bounds = c.Rect;
if (pnt.Y < bounds.Bottom)
return i;
}
return elements.Count - 1;
}
static int ComputeIdxGrid(EnumerableEx elements, Point pnt)
{
if (pnt.X <= 0 && pnt.Y <= 0)
return 0;
if (pnt.X == Double.PositiveInfinity && pnt.Y == Double.PositiveInfinity)
return elements.Count - 1;
double prevLeft = double.NegativeInfinity;
int verticalLastHit = -1;
foreach ((var c, int i) in elements.Select((value, index) => (value, index)))
{
var bounds = c.Rect;
if (pnt.Y < bounds.Bottom)
{
if (pnt.X < bounds.Right)
return i;
if (bounds.Left < prevLeft)
return verticalLastHit;
prevLeft = bounds.Left;
verticalLastHit = i;
}
}
return elements.Count - 1;
}
private static List Select(EnumerableEx elements, Point from, Point to, int fp, int tp)
{
var list = new List();
if (fp < tp)
{
var workF = from;
var workT = new Point(Double.PositiveInfinity, Double.PositiveInfinity);
for (var i = fp; i <= tp; ++i)
{
if (i == tp)
{
workT = to;
}
var element = elements[i].Element;
var rect = elements[i].Rect;
element.Select(
new Point(workF.X - rect.X, workF.Y - rect.Y),
new Point(workT.X - rect.X, workT.Y - rect.Y));
list.Add(element);
workF = new Point(0, 0);
}
}
else if (tp < fp)
{
var workF = from;
var workT = new Point(0, 0);
for (var i = fp; i >= tp; --i)
{
if (i == tp)
{
workT = to;
}
var element = elements[i].Element;
var rect = elements[i].Rect;
element.Select(
new Point(workF.X - rect.X, workF.Y - rect.Y),
new Point(workT.X - rect.X, workT.Y - rect.Y));
list.Add(element);
workF = new Point(Double.PositiveInfinity, Double.PositiveInfinity);
}
}
else
{
var element = elements[tp].Element;
var rect = elements[tp].Rect;
element.Select(
new Point(from.X - rect.X, from.Y - rect.Y),
new Point(to.X - rect.X, to.Y - rect.Y));
list.Add(element);
}
return list;
}
}
}
================================================
FILE: ColorTextBlock.Avalonia/CBold.cs
================================================
using System.Collections.Generic;
using Weight = Avalonia.Media.FontWeight;
namespace ColorTextBlock.Avalonia
{
///
/// Bold decoration
///
public class CBold : CSpan
{
public CBold() { }
public CBold(IEnumerable inlines) : base(inlines)
{
FontWeight = Weight.Bold;
}
}
}
================================================
FILE: ColorTextBlock.Avalonia/CCode.cs
================================================
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia;
using ColorTextBlock.Avalonia.Fonts;
namespace ColorTextBlock.Avalonia
{
///
/// Monospace decoration
///
public class CCode : CSpan
{
///
/// Monospace font family used for code display.
///
///
public static readonly StyledProperty MonospaceFontFamilyProperty =
AvaloniaProperty.Register(
nameof(MonospaceFontFamily),
defaultValue: FontFamilyCollector.TryGetMonospace() ?? FontFamily.Default,
inherits: true);
public CCode() {
var obsvr = this.GetBindingObservable(MonospaceFontFamilyProperty);
Bind(FontFamilyProperty, obsvr);
}
public CCode(IEnumerable inlines) : base(inlines)
{
var obsvr = this.GetBindingObservable(MonospaceFontFamilyProperty);
Bind(FontFamilyProperty, obsvr);
}
///
/// Monospace font family used for code display.
///
public FontFamily MonospaceFontFamily
{
get { return GetValue(MonospaceFontFamilyProperty); }
set { SetValue(MonospaceFontFamilyProperty, value); }
}
}
}
================================================
FILE: ColorTextBlock.Avalonia/CHyperlink.cs
================================================
using Avalonia;
using Avalonia.Input;
using Avalonia.Media;
using ColorTextBlock.Avalonia.Geometries;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ColorTextBlock.Avalonia
{
///
/// Hyperlink decoration
///
public class CHyperlink : CSpan
{
///
/// Background brush during mouse hover
///
///
public static readonly StyledProperty HoverBackgroundProperty =
AvaloniaProperty.Register(nameof(Foreground));
///
/// Foreground brush during mouse hover
///
///
public static readonly StyledProperty HoverForegroundProperty =
AvaloniaProperty.Register(nameof(Foreground));
///
/// Background brush during mouse hover
///
public IBrush? HoverBackground
{
get { return GetValue(HoverBackgroundProperty); }
set { SetValue(HoverBackgroundProperty, value); }
}
///
/// Foreground brush during mouse hover
///
public IBrush? HoverForeground
{
get { return GetValue(HoverForegroundProperty); }
set { SetValue(HoverForegroundProperty, value); }
}
///
/// Link click action
///
public Action? Command { get; set; }
///
/// Link click action parameter
///
public string? CommandParameter { get; set; }
public CHyperlink() { }
public CHyperlink(IEnumerable inlines) : base(inlines)
{
}
protected override IEnumerable MeasureOverride(
double entireWidth,
double remainWidth)
{
var metrics = base.MeasureOverride(
entireWidth,
remainWidth);
foreach (CGeometry metry in metrics)
{
metry.OnClick = ctrl => Command?.Invoke(CommandParameter ?? string.Empty);
metry.OnMousePressed = ctrl =>
{
PseudoClasses.Add(":pressed");
};
metry.OnMouseReleased = ctrl =>
{
PseudoClasses.Remove(":pressed");
};
metry.OnMouseEnter = ctrl =>
{
PseudoClasses.Add(":pointerover");
PseudoClasses.Add(":hover");
try
{
ctrl.Cursor = new Cursor(StandardCursorType.Hand);
}
catch { /*I cannot assume Cursor.ctor doesn't throw an exception.*/ }
IEnumerable tmetries =
(metry is DecoratorGeometry d) ?
d.Targets.OfType() :
(metry is TextGeometry t) ?
new[] { t } :
new TextGeometry[0];
if (tmetries != null)
{
foreach (var tmetry in tmetries)
{
tmetry.TemporaryForeground = HoverForeground;
tmetry.TemporaryBackground = HoverBackground;
}
RequestRender();
}
};
metry.OnMouseLeave = ctrl =>
{
PseudoClasses.Remove(":pointerover");
PseudoClasses.Remove(":hover");
ctrl.Cursor = Cursor.Default;
IEnumerable tmetries =
(metry is DecoratorGeometry d) ?
d.Targets.OfType() :
(metry is TextGeometry t) ?
new[] { t } :
new TextGeometry[0];
if (tmetries != null)
{
foreach (var tmetry in tmetries)
{
tmetry.TemporaryForeground = null;
tmetry.TemporaryBackground = null;
}
RequestRender();
}
};
yield return metry;
}
}
}
}
================================================
FILE: ColorTextBlock.Avalonia/CImage.cs
================================================
using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using ColorTextBlock.Avalonia.Geometries;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Media;
namespace ColorTextBlock.Avalonia
{
///
/// Displays an image
///
public class CImage : CInline
{
public static readonly StyledProperty LayoutWidthProperty =
AvaloniaProperty.Register(nameof(LayoutWidth));
public static readonly StyledProperty LayoutHeightProperty =
AvaloniaProperty.Register(nameof(LayoutHeight));
public static readonly StyledProperty RelativeWidthProperty =
AvaloniaProperty.Register(nameof(RelativeWidth));
///
/// Determine wheither image auto fitting or protrude outside Control
/// when image is too width to be rendered in control.
/// If you set 'true', Image is fitted to control width.
///
public static readonly StyledProperty FittingWhenProtrudeProperty =
AvaloniaProperty.Register(nameof(FittingWhenProtrude), defaultValue: true);
///
/// Save aspect ratio if one of or set.
///
public static readonly StyledProperty SaveAspectRatioProperty =
AvaloniaProperty.Register(nameof(SaveAspectRatio));
public double? LayoutWidth
{
get { return GetValue(LayoutWidthProperty); }
set { SetValue(LayoutWidthProperty, value); }
}
public double? LayoutHeight
{
get { return GetValue(LayoutHeightProperty); }
set { SetValue(LayoutHeightProperty, value); }
}
public double? RelativeWidth
{
get { return GetValue(RelativeWidthProperty); }
set { SetValue(RelativeWidthProperty, value); }
}
public bool FittingWhenProtrude
{
get { return GetValue(FittingWhenProtrudeProperty); }
set { SetValue(FittingWhenProtrudeProperty, value); }
}
public bool SaveAspectRatio
{
get => GetValue(SaveAspectRatioProperty);
set => SetValue(SaveAspectRatioProperty, value);
}
public Task? Task { get; }
private IImage WhenError { get; }
public IImage? Image { private set; get; }
public CImage(Task task, IImage whenError)
{
if (task is null) throw new NullReferenceException(nameof(task));
if (whenError is null) throw new NullReferenceException(nameof(whenError));
this.Task = task;
this.WhenError = whenError;
}
public CImage(IImage image)
{
if (image is null) throw new NullReferenceException(nameof(image));
this.WhenError = this.Image = image;
}
protected override IEnumerable MeasureOverride(
double entireWidth, double remainWidth)
{
if (Image is null)
{
if (Task is null)
{
Image = WhenError;
}
else if (
Task.Status == TaskStatus.RanToCompletion
|| Task.Status == TaskStatus.Faulted
|| Task.Status == TaskStatus.Canceled)
{
Image = Task.IsFaulted ? WhenError : Task.Result ?? WhenError;
}
else
{
Image = new WriteableBitmap(
new PixelSize(1, 1),
new Vector(96, 96),
PixelFormat.Rgb565,
AlphaFormat.Premul);
Thread.MemoryBarrier();
System.Threading.Tasks.Task.Run(() =>
{
Task.Wait();
Dispatcher.UIThread.InvokeAsync(() =>
{
Image = Task.IsFaulted ? WhenError : Task.Result ?? WhenError;
RequestMeasure();
});
});
}
}
double imageWidth = Image.Size.Width;
double imageHeight = Image.Size.Height;
if (RelativeWidth.HasValue)
{
var aspect = imageHeight / imageWidth;
imageWidth = RelativeWidth.Value * entireWidth;
imageHeight = aspect * imageWidth;
}
if (LayoutWidth.HasValue)
{
imageWidth = LayoutWidth.Value;
if (SaveAspectRatio && !LayoutHeight.HasValue)
{
var aspect = Image.Size.Height / Image.Size.Width;
imageHeight = aspect * imageWidth;
}
}
if (LayoutHeight.HasValue)
{
imageHeight = LayoutHeight.Value;
if (SaveAspectRatio && !LayoutWidth.HasValue)
{
var aspect = Image.Size.Width / Image.Size.Height;
imageWidth = aspect * imageHeight;
}
}
if (imageWidth > remainWidth)
{
if (entireWidth != remainWidth)
{
yield return new LineBreakMarkGeometry(this);
}
if (FittingWhenProtrude && imageWidth > entireWidth)
{
var aspect = imageHeight / imageWidth;
imageWidth = entireWidth;
imageHeight = aspect * imageWidth;
}
}
yield return new ImageGeometry(this, Image, imageWidth, imageHeight, TextVerticalAlignment);
}
public override string AsString() => " $$Image$$ ";
}
}
================================================
FILE: ColorTextBlock.Avalonia/CInline.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using ColorTextBlock.Avalonia.Geometries;
using System.Collections.Generic;
using System.ComponentModel;
namespace ColorTextBlock.Avalonia
{
///
/// The base class for representing a text element.
///
// テキスト要素を表現するための基底のクラス
[TypeConverter(typeof(StringToRunConverter))]
public abstract class CInline : StyledElement
{
///
/// The brush of background.
///
///
public static readonly StyledProperty BackgroundProperty =
AvaloniaProperty.Register(nameof(Background), inherits: true);
///
/// The brush of the text element.
///
///
public static readonly StyledProperty ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner();
///
/// The font family of the text element
///
///
public static readonly StyledProperty FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner();
///
/// The font weight of the text element
///
///
public static readonly StyledProperty FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner();
///
/// The font stretch of the text element
///
///
public static readonly StyledProperty FontStretchProperty =
TextBlock.FontStretchProperty.AddOwner();
///
/// The font size of the text element
///
///
public static readonly StyledProperty FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner();
///
/// The font style of the text element
///
///
public static readonly StyledProperty FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner();
///
/// Use to indicate the vertical position of text within line.
/// For example, it is used to align text to the top or to the bottom.
///
///
// テキストを上揃えで描画するか下揃えで描画するか指定します。
public static readonly StyledProperty TextVerticalAlignmentProperty =
CTextBlock.TextVerticalAlignmentProperty.AddOwner();
///
/// Indicates whether the text element is underlined.
/// If this property value is true, the text element is underlined.
///
///
public static readonly StyledProperty IsUnderlineProperty =
AvaloniaProperty.Register(nameof(IsUnderline), inherits: true);
///
/// Indicates whether the text element is strikethrough.
/// If the value of this property is true, the text element is strikethrough.
///
///
public static readonly StyledProperty IsStrikethroughProperty =
AvaloniaProperty.Register(nameof(IsStrikethrough), inherits: true);
///
/// The brush of background.
///
public IBrush? Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
///
/// The brush of the text element.
///
public IBrush? Foreground
{
get { return GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
///
/// The font family of the text element
///
public FontFamily FontFamily
{
get { return GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
///
/// The font size of the text element
///
public double FontSize
{
get { return GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
///
/// The font stretch of the text element
///
public FontStyle FontStyle
{
get { return GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
///
/// The font weight of the text element
///
public FontWeight FontWeight
{
get { return GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
///
/// The font stretch of the text element
///
public FontStretch FontStretch
{
get { return GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
///
/// Typeface of the text element
///
public Typeface Typeface
{
get;
private set;
}
///
/// Indicates whether the text element is underlined.
/// If this property value is true, the text element is underlined.
///
public bool IsUnderline
{
get { return GetValue(IsUnderlineProperty); }
set { SetValue(IsUnderlineProperty, value); }
}
///
/// Indicates whether the text element is strikethrough.
/// If the value of this property is true, the text element is strikethrough.
///
public bool IsStrikethrough
{
get { return GetValue(IsStrikethroughProperty); }
set { SetValue(IsStrikethroughProperty, value); }
}
///
/// Use to indicate the vertical position of text within line.
/// For example, it is used to align text to the top or to the bottom.
///
public TextVerticalAlignment TextVerticalAlignment
{
get { return GetValue(TextVerticalAlignmentProperty); }
set { SetValue(TextVerticalAlignmentProperty, value); }
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
switch (change.Property.Name)
{
case nameof(Background):
case nameof(Foreground):
case nameof(IsUnderline):
case nameof(IsStrikethrough):
RequestRender();
break;
case nameof(FontFamily):
case nameof(FontSize):
case nameof(FontStyle):
case nameof(FontWeight):
case nameof(FontStretch):
Typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
goto case nameof(TextVerticalAlignment);
case nameof(TextVerticalAlignment):
RequestMeasure();
break;
}
}
protected void RequestMeasure()
{
if (Parent is CInline cline)
{
cline.RequestMeasure();
}
else if (Parent is CTextBlock ctxt)
{
ctxt.OnMeasureSourceChanged();
}
else if (Parent is Layoutable layout)
{
layout.InvalidateMeasure();
}
}
protected void RequestRender()
{
try
{
if (Parent is CInline cline)
{
cline.RequestRender();
}
else if (Parent is Layoutable layout)
{
layout.InvalidateVisual();
}
}
catch
{
// An error occured sometimes with FluentAvalonia.
}
}
internal IEnumerable Measure(double entireWidth, double remainWidth)
{
Typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
/*
* This is Imitation of Layoutable.MeasureCore.
* If parent style is changed, StyledElement.InvalidedStyles is called.
* This method clear all applied styles,
* so we should reapply style after style change.
*/
ApplyStyling();
return MeasureOverride(entireWidth, remainWidth);
}
protected abstract IEnumerable MeasureOverride(
double entireWidth,
double remainWidth);
///
/// Returns the string that this instance displays.
///
///
// この要素が表示する文字を返します。
public abstract string AsString();
}
}
================================================
FILE: ColorTextBlock.Avalonia/CInlineUIContainer.cs
================================================
using Avalonia;
using Avalonia.Controls;
using ColorTextBlock.Avalonia.Geometies;
using ColorTextBlock.Avalonia.Geometries;
using System;
using System.Collections.Generic;
using System.Text;
namespace ColorTextBlock.Avalonia
{
///
/// Places a control as an inline element.
///
public class CInlineUIContainer : CInline
{
///
/// A displayed control
///
public Control? Content { get; set; }
internal DummyGeometryForControl? Indicator { get; private set; }
public CInlineUIContainer(Control content)
{
Content = content;
}
protected override IEnumerable MeasureOverride(double entireWidth, double remainWidth)
{
if (Content is null)
{
Indicator = null;
return new CGeometry[0];
}
Content.Measure(new Size(remainWidth, Double.PositiveInfinity));
if (Content.DesiredSize.Width > remainWidth)
{
Content.Measure(new Size(entireWidth, Double.PositiveInfinity));
Indicator = new DummyGeometryForControl(this, Content, TextVerticalAlignment);
return new CGeometry[] { new LineBreakMarkGeometry(this), Indicator };
}
else
{
Indicator = new DummyGeometryForControl(this, Content, TextVerticalAlignment);
return new[] { Indicator };
}
}
///
public override string AsString() => String.Empty;
}
}
================================================
FILE: ColorTextBlock.Avalonia/CItalic.cs
================================================
using System.Collections.Generic;
using FStyle = Avalonia.Media.FontStyle;
namespace ColorTextBlock.Avalonia
{
///
/// Italic decoration
///
public class CItalic : CSpan
{
public CItalic() { }
public CItalic(IEnumerable inlines) : base(inlines)
{
FontStyle = FStyle.Italic;
}
}
}
================================================
FILE: ColorTextBlock.Avalonia/CLineBreak.cs
================================================
using Avalonia;
using Avalonia.Media;
using ColorTextBlock.Avalonia.Geometries;
using System.Collections.Generic;
using System.Globalization;
namespace ColorTextBlock.Avalonia
{
///
/// Expression of the linebreak.
///
public class CLineBreak : CRun
{
public CLineBreak()
{
Text = "\n";
}
protected override IEnumerable MeasureOverride(
double entireWidth,
double remainWidth)
{
var ftxt = new FormattedText(
"Ty",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(FontFamily, FontStyle, FontWeight),
FontSize,
Foreground);
yield return new LineBreakMarkGeometry(this, ftxt.Height);
}
}
}
================================================
FILE: ColorTextBlock.Avalonia/CRun.cs
================================================
using Avalonia;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Metadata;
using ColorTextBlock.Avalonia.Geometries;
using System;
using System.Collections.Generic;
namespace ColorTextBlock.Avalonia
{
///
/// Expression of a text
///
public class CRun : CInline
{
///
/// THe content of the eleemnt
///
///
public static readonly StyledProperty TextProperty =
AvaloniaProperty.Register(nameof(Text));
///
/// THe content of the eleemnt
///
[Content]
public string Text
{
get { return GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
protected override IEnumerable MeasureOverride(
double entireWidth,
double remainWidth)
{
if (String.IsNullOrEmpty(Text))
{
return Array.Empty();
}
var runProps = CreateTextRunProperties(Foreground);
var paraProps = CreateTextParagraphProperties(runProps);
var source = new SimpleTextSource(Text.AsMemory(), runProps);
if (remainWidth == entireWidth)
{
return CreateLines(source, entireWidth, paraProps);
}
var firstLine = TextFormatter.Current.FormatLine(source, 0, double.PositiveInfinity, paraProps);
if (firstLine is null)
{
return Array.Empty();
}
if (firstLine.Width < remainWidth)
{
if (firstLine.Length == Text.Length)
{
return new List() { new TextLineGeometry(this, source, firstLine, false) };
}
return CreateLines(source, entireWidth, paraProps, firstLine);
}
else
{
var firstLineSource = source.Subsource(firstLine.FirstTextSourceIndex, firstLine.Length);
var firstLineRemain = TextFormatter.Current.FormatLine(firstLineSource, 0, remainWidth, paraProps)!;
var breakPosEnum = new LineBreakEnumerator(Text.AsMemory().Slice(firstLine.FirstTextSourceIndex, firstLine.Length).Span);
int breakPos = breakPosEnum.MoveNext(out var lnbrk) ? lnbrk.PositionWrap : int.MaxValue;
if (breakPos < firstLineRemain.Length)
{
// correct wrap
return CreateLines(source, entireWidth, paraProps, firstLineRemain);
}
else
{
// wrong wrap; first line word is too long
return CreateLines(source, entireWidth, paraProps, new LineBreakMarkGeometry(this));
}
}
}
private IEnumerable CreateLines(
SimpleTextSource source,
double entireWidth,
TextParagraphProperties paraProps,
TextLine firstLine)
{
TextLine prev = firstLine;
var length = firstLine.Length;
while (length < Text.Length)
{
var line = TextFormatter.Current.FormatLine(source, length, entireWidth, paraProps, prev.TextLineBreak);
if (line is null)
break;
yield return new TextLineGeometry(this, source, prev, true);
prev = line;
length += line.Length;
}
yield return new TextLineGeometry(this, source, prev, false);
}
private IEnumerable CreateLines(
SimpleTextSource source,
double entireWidth,
TextParagraphProperties paraProps,
CGeometry? prevGeo = null)
{
if (prevGeo is not null)
yield return prevGeo;
TextLine? prev = TextFormatter.Current.FormatLine(source, 0, entireWidth, paraProps);
if (prev is null)
yield break;
var length = prev.Length;
while (length < Text.Length)
{
var line = TextFormatter.Current.FormatLine(source, length, entireWidth, paraProps, prev.TextLineBreak);
if (line is null)
break;
yield return new TextLineGeometry(this, source, prev, true);
prev = line;
length += line.Length;
}
yield return new TextLineGeometry(this, source, prev, false);
}
internal TextParagraphProperties CreateTextParagraphProperties(TextRunProperties runProps)
=> new GenericTextParagraphProperties(
FlowDirection.LeftToRight,
TextAlignment.Left, true, false,
runProps,
TextWrapping.Wrap,
double.NaN,
0,
0);
internal TextRunProperties CreateTextRunProperties(IBrush? foreground)
=> new GenericTextRunProperties(Typeface, FontSize, foregroundBrush: foreground);
public override string AsString() => Text;
}
readonly struct SimpleTextSource : ITextSource
{
private readonly ReadOnlyMemory _text;
private readonly TextRunProperties _props;
public TextRunProperties RunProperties => _props;
public SimpleTextSource(ReadOnlyMemory text, TextRunProperties props)
{
_text = text;
_props = props;
}
public TextRun? GetTextRun(int textSourceIndex)
{
return new TextCharacters(_text.Slice(textSourceIndex), _props);
}
public SimpleTextSource Subsource(int start, int length)
=> new SimpleTextSource(_text.Slice(start, length), _props);
public string Substring(int start, int length)
=> _text.Slice(start, length).ToString();
public string Substring(int start)
=> _text.Slice(start).ToString();
public SimpleTextSource ChangeForeground(IBrush? foreground)
{
var runProps = new GenericTextRunProperties(_props.Typeface, _props.FontRenderingEmSize, foregroundBrush: foreground);
return new SimpleTextSource(_text, runProps);
}
public override string ToString() => _text.ToString();
}
}
================================================
FILE: ColorTextBlock.Avalonia/CSpan.cs
================================================
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Metadata;
using ColorTextBlock.Avalonia.Geometries;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ColorTextBlock.Avalonia
{
///
/// Text decoration
///
public class CSpan : CInline
{
///
/// The thickness of the border
///
///
public static readonly StyledProperty BorderThicknessProperty =
AvaloniaProperty.Register(nameof(BorderThickness));
///
/// The brush of the border.
///
///
public static readonly StyledProperty BorderBrushProperty =
AvaloniaProperty.Register(nameof(BorderBrush));
///
/// The radius of the border rounded corners
///
///
public static readonly StyledProperty CornerRadiusProperty =
AvaloniaProperty.Register(nameof(CornerRadius));
///
/// The box shadow effect parameters
///
///
public static readonly StyledProperty BoxShadowProperty =
AvaloniaProperty.Register(nameof(BoxShadow));
///
/// The padding to place around the Child control.
///
///
public static readonly StyledProperty PaddingProperty =
AvaloniaProperty.Register(nameof(Padding));
///
/// The margin around the element.
///
///
public static readonly StyledProperty MarginProperty =
InputElement.MarginProperty.AddOwner();
///
/// THe content of the eleemnt
///
///
public static readonly StyledProperty> ContentProperty =
AvaloniaProperty.Register>(nameof(Content));
static CSpan()
{
ContentProperty.Changed.AddClassHandler(
(x, e) =>
{
if (e.OldValue is IEnumerable oldlines)
{
foreach (var child in oldlines)
x.LogicalChildren.Remove(child);
}
if (e.NewValue is IEnumerable newlines)
{
foreach (var child in newlines)
x.LogicalChildren.Add(child);
}
});
}
private Border? _border;
///
/// The thickness of the border
///
public Thickness BorderThickness
{
get => GetValue(BorderThicknessProperty);
set => SetValue(BorderThicknessProperty, value);
}
///
/// The brush of the border.
///
public IBrush BorderBrush
{
get => GetValue(BorderBrushProperty);
set => SetValue(BorderBrushProperty, value);
}
///
/// The radius of the border rounded corners
///
public CornerRadius CornerRadius
{
get => GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
///
/// The box shadow effect parameters
///
public BoxShadows BoxShadow
{
get => GetValue(BoxShadowProperty);
set => SetValue(BoxShadowProperty, value);
}
///
/// The padding to place around the Child control.
///
public Thickness Padding
{
get => GetValue(PaddingProperty);
set => SetValue(PaddingProperty, value);
}
///
/// The margin around the element.
///
public Thickness Margin
{
get => GetValue(MarginProperty);
set => SetValue(MarginProperty, value);
}
///
/// THe content of the eleemnt
///
[Content]
public IEnumerable Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public CSpan()
{
var clst = new AvaloniaList();
// for xaml loader
clst.CollectionChanged += (s, e) =>
{
if (e.OldItems != null)
foreach (var child in e.OldItems)
LogicalChildren.Remove((CInline)child);
if (e.NewItems != null)
foreach (var child in e.NewItems)
LogicalChildren.Add((CInline)child);
};
Content = clst;
}
public CSpan(IEnumerable inlines)
{
Content = inlines.ToArray();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
switch (change.Property.Name)
{
case nameof(BorderThickness):
case nameof(CornerRadius):
case nameof(BoxShadow):
case nameof(Padding):
case nameof(Margin):
OnBorderPropertyChanged(true);
break;
case nameof(BorderBrush):
OnBorderPropertyChanged(false);
break;
}
}
private void OnBorderPropertyChanged(bool requestMeasure)
{
bool borderEnabled =
BorderThickness != default ||
Padding != default ||
CornerRadius != default ||
Margin != default ||
!BoxShadow.Equals(default);
if (borderEnabled)
{
if (_border is null)
{
_border = new Border();
LogicalChildren.Add(_border);
}
_border.BorderThickness = BorderThickness;
_border.BorderBrush = BorderBrush;
_border.CornerRadius = CornerRadius;
_border.BoxShadow = BoxShadow;
_border.Padding = Padding;
_border.Margin = Margin;
}
else
{
if (_border is not null)
LogicalChildren.Remove(_border);
_border = null;
}
if (requestMeasure) RequestMeasure();
else RequestRender();
}
protected override IEnumerable MeasureOverride(
double entireWidth,
double remainWidth)
{
if (_border is not null)
{
_border.Measure(Size.Infinity);
entireWidth -= _border.DesiredSize.Width;
remainWidth -= _border.DesiredSize.Width;
return PrivateMeasure(_border, entireWidth, remainWidth);
}
else
{
return PrivateMeasure(entireWidth, remainWidth);
}
}
private IEnumerable PrivateMeasure(
Border border,
double entireWidth,
double remainWidth)
{
var buffer = new List();
foreach (var adding in PrivateMeasure(entireWidth, remainWidth))
{
// save linebreak before span
if (adding is LineBreakMarkGeometry && buffer.Count == 0)
{
yield return adding;
continue;
}
buffer.Add(adding);
if (adding.LineBreak)
{
yield return DecoratorGeometry.New(this, buffer, border);
buffer.Clear();
}
}
if (buffer.Count != 0)
{
yield return DecoratorGeometry.New(this, buffer, border);
}
}
private IEnumerable PrivateMeasure(
double entireWidth,
double remainWidth)
{
foreach (CInline inline in Content)
{
IEnumerable addings = inline.Measure(entireWidth, remainWidth);
foreach (var add in addings)
{
yield return add;
if (add.LineBreak) remainWidth = entireWidth;
else remainWidth -= add.Width;
}
}
}
public override string AsString() => String.Join("", Content.Select(c => c.AsString()));
}
}
================================================
FILE: ColorTextBlock.Avalonia/CStrikethrough.cs
================================================
using System.Collections.Generic;
namespace ColorTextBlock.Avalonia
{
///
/// Strikethrough decoration
///
public class CStrikethrough : CSpan
{
public CStrikethrough() { }
public CStrikethrough(IEnumerable inlines) : base(inlines)
{
IsStrikethrough = true;
}
}
}
================================================
FILE: ColorTextBlock.Avalonia/CTextBlock.cs
================================================
using Avalonia;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Metadata;
using Avalonia.Rendering.Composition;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using ColorTextBlock.Avalonia.Geometries;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace ColorTextBlock.Avalonia
{
///
/// TextBlock to enables character-by-character decoration.
///
// 文字ごとの装飾を可能とするTextBlock
public class CTextBlock : Control, ITextPointerHandleable
{
///
/// Use for adjusting vertical position between CTextBlocks. e.g. between a list marker and a list item.
///
// リストマーカーと項目の縦位置の調整といった、CTextBlock間で文字の位置調整に使用します。
private static readonly StyledProperty BaseHeightProperty =
AvaloniaProperty.Register("BaseHeight");
///
/// Use to indicate the height of each lines. If this value is NaN, the height is calculated by content.
///
///
// 一行の高さ指定の為に使用します。指定がない(NaN)の場合、コンテンツによって行の高さが決まります。
public static readonly StyledProperty LineHeightProperty =
AvaloniaProperty.Register(nameof(LineHeight), defaultValue: Double.NaN);
///
/// Line to line spacing.
///
///
// 行間の幅
public static readonly StyledProperty LineSpacingProperty =
AvaloniaProperty.Register(nameof(LineSpacing), defaultValue: 0);
///
/// The brush of background.
///
///
public static readonly StyledProperty BackgroundProperty =
Border.BackgroundProperty.AddOwner();
///
/// The brush of characters.
///
///
public static readonly StyledProperty ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner();
///
/// The font family of characters
///
///
public static readonly StyledProperty FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner();
///
/// The font weight of characters
///
///
public static readonly StyledProperty FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner();
///
/// The font size of characters
///
///
public static readonly StyledProperty FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner();
///
/// The font style of characters
///
///
public static readonly StyledProperty FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner();
///
/// Use to indicate the vertical position of text within line.
/// For example, it is used to align text to the top or to the bottom.
///
///
// テキストを上揃えで描画するか下揃えで描画するか指定します。
public static readonly StyledProperty TextVerticalAlignmentProperty =
AvaloniaProperty.Register(
nameof(TextVerticalAlignment),
defaultValue: TextVerticalAlignment.Base,
inherits: true);
///
/// Use to indicate the mode of text wrapping.
///
///
public static readonly StyledProperty TextWrappingProperty =
AvaloniaProperty.Register(nameof(TextWrapping), defaultValue: TextWrapping.Wrap);
///
/// Contents to be displayed.
///
///
public static readonly DirectProperty> ContentProperty =
AvaloniaProperty.RegisterDirect>(
nameof(Content),
o => o.Content,
(o, v) => o.Content = v);
public static readonly StyledProperty SelectionBrushProperty =
SelectableTextBlock.SelectionBrushProperty.AddOwner();
///
/// Horizontal text alignment.
///
///
public static readonly StyledProperty TextAlignmentProperty =
AvaloniaProperty.Register(
nameof(TextAlignment), defaultValue: TextAlignment.Left);
static CTextBlock()
{
ClipToBoundsProperty.OverrideDefaultValue(true);
AffectsRender(
BackgroundProperty,
TextBlock.ForegroundProperty,
TextBlock.FontWeightProperty,
TextBlock.FontSizeProperty,
TextBlock.FontStyleProperty);
}
private double _computedBaseHeight;
private AvaloniaList _content;
private Size _constraint;
private Size _measured;
private readonly List _metries;
private readonly List _lines;
private readonly List _containers;
private CGeometry? _entered;
private CGeometry? _pressed;
private string? _text;
private bool _measureRequested;
private TextPointer? _beginSelect;
private List _intermediates = new();
private TextPointer? _endSelect;
public Selection? Selection =>
_beginSelect is not null && _endSelect is not null ?
new Selection(_beginSelect.Index, _endSelect.Index) :
null;
///
/// The brush of background.
///
public IBrush? Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
///
/// The brush of characters.
///
public IBrush? Foreground
{
get { return GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
///
/// The font family of characters
///
public FontFamily FontFamily
{
get { return GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
///
/// The font size of characters
///
public double FontSize
{
get { return GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
///
/// The font style of characters
///
public FontStyle FontStyle
{
get { return GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
///
/// The font weight of characters
///
public FontWeight FontWeight
{
get { return GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
///
/// Use to indicate the mode of text wrapping.
///
public TextWrapping TextWrapping
{
get { return GetValue(TextWrappingProperty); }
set { SetValue(TextWrappingProperty, value); }
}
///
/// Horizontal text alignment.
///
public TextAlignment TextAlignment
{
get { return GetValue(TextAlignmentProperty); }
set { SetValue(TextAlignmentProperty, value); }
}
///
/// Use to indicate the vertical position of text within line.
/// For example, it is used to align text to the top or to the bottom.
///
public TextVerticalAlignment TextVerticalAlignment
{
get { return GetValue(TextVerticalAlignmentProperty); }
set { SetValue(TextVerticalAlignmentProperty, value); }
}
///
/// Use to indicate the height of each lines. If this value is NaN, the height is calculated by content.
///
public double LineHeight
{
get { return GetValue(LineHeightProperty); }
set { SetValue(LineHeightProperty, value); }
}
///
/// Line to line spacing.
///
public double LineSpacing
{
get { return GetValue(LineSpacingProperty); }
set { SetValue(LineSpacingProperty, value); }
}
///
/// Contents to be displayed.
///
[Content]
public AvaloniaList Content
{
get => _content;
set
{
var olds = _content;
if (SetAndRaise(ContentProperty, ref _content, value))
{
olds.CollectionChanged -= ContentCollectionChangedd;
DetachChildren(olds);
AttachChildren(_content);
_content.CollectionChanged += ContentCollectionChangedd;
}
}
}
///
/// Textual presentation of content.
///
public string Text
{
get => _text ??= String.Join("", Content.Select(c => c.AsString()));
}
public IBrush? SelectionBrush
{
get => GetValue(SelectionBrushProperty);
set => SetValue(SelectionBrushProperty, value);
}
public CTextBlock()
{
_content = new AvaloniaList();
_content.CollectionChanged += ContentCollectionChangedd;
_metries = new List();
_lines = new List();
_containers = new List();
RenderOptions.SetBitmapInterpolationMode(this, BitmapInterpolationMode.HighQuality);
}
public CTextBlock(string text) : this()
{
_content.Add(new CRun() { Text = text });
}
public CTextBlock(params CInline[] inlines) : this((IEnumerable)inlines)
{
}
public CTextBlock(IEnumerable