Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

File diff suppressed because one or more lines are too long

250
vendor/cosmic-text/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,250 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.13.2] - 2025-03-11
### Fixed
- Fix build for Android targets
## [0.13.1] - 2025-03-10
### Fixed
- Fix glyph start+end indices in `Basic` shaping
## [0.13.0] - 2025-03-10
### Added
- Add `Buffer::set_tab_width` function
- Add `AttrsList::spans_iter` and use it in `Buffer::append`
- Add alignment option to `Buffer::set_rich_text`
- Add `SwashCache::get_outline_commands_uncached`
### Fixed
- Fix typo in fallback font name `Noto Sans CJK JP`
- Fix the character index used for getting a glyph attribute in basic shaping
- Avoid debug assertion when handling `Motion::BufferEnd`
- Handle single wrapped line scrolling
- Reduce memory usage and loading time of FontSystem
- Resolve lints
- Use FreeMono as Braille script fallback
- Load fonts prior to setting defaults
### Changed
- Use `smol_str` for family name in `FamilyOwned`
- Optimize `Buffer::set_rich_text` for when the buffer is reconstructed
- Move `ShapeBuffer` to `FontSystem`
- Cache the monospace fallbacks buffer in `FontSystem`
- Apply fallback font to more Unix-like operating systems
- Use hinting for `swash_outline_commands`
- Update swash to `0.2.0` and hook up `std` feature
- Update minimum supported Rust version to `1.75`
- Update default fonts (for COSMIC, users should set their own as desired)
### Removed
- Drop `ShapePlanCache`
## [0.12.1] - 2024-06-31
### Changed
- Make collection of monospace fallback information optional
## [0.12.0] - 2024-06-18
### Added
- Cache codepoint support info for monospace fonts
- Store a sorted list of monospace font ids in font system
- Add line ending abstraction
- Horizontal scroll support in Buffer
- Concurrently load and parse fonts
- Add metrics to attributes
- Support expanding tabs
- Add an option to set selected text color
- Add Edit::cursor_position
- Allow layout to be calculated without specifying width
- Allow for undefined buffer width and/or height
- Add method to set syntax highlighting by file extension
### Fixed
- Fix no_std build
- Handle inverted Ranges in add_span
- Fix undo and redo updating editor modified status
- Ensure at least one line is in Buffer
### Changed
- Enable vi feature for docs.rs build
- Convert editor example to winit
- Refactor scrollbar width handling for editor example
- Convert rich-text example to winit
- Only try monospace fonts that support at least one requested script
- Skip trying monospace fallbacks if default font supports all codepoints
- Make vertical scroll by pixels instead of layout lines
- Upgrade dependencies and re-export ttf-parser
## [0.11.2] - 2024-02-08
### Fixed
- Fix glyph start and end when using `shape-run-cache`
## [0.11.1] - 2024-02-08
### Added
- Add `shape-run-cache` feature, that can significantly improve shaping performance
### Removed
- Remove editor-libcosmic, see cosmic-edit instead
## [0.11.0] - 2024-02-07
### Added
- Add function to set metrics and size simultaneously
- Cache `rustybuzz` shape plans
- Add capability to synthesize italic
- New wrapping option `WordOrGlyph` to allow word to glyph fallback
### Fixed
- `Buffer::set_rich_text`: Only add attrs if they do not match the defaults
- Do not use Emoji fonts as monospace fallback
- Refresh the attrs more often in basic shaping
- `Buffer`: fix max scroll going one line beyond end
- Improve reliability of `layout_cursor`
- Handle multiple BiDi paragraphs in `ShapeLine` gracefully
- Improved monospace font fallback
- Only commit a previous word range if we had an existing visual line
### Changed
- Update terminal example using `colored`
- Significant improvements for `Editor`, `SyntaxEditor`, and `ViEditor`
- Require default Attrs to be specified in `Buffer::set_rich_text`
- Bump `fontdb` to `0.16`
- Allow Clone of layout structs
- Move cursor motions to new `Motion` enum, move handling to `Buffer`
- Ensure that all shaping and layout uses scratch buffer
- `BufferLine`: user `layout_in_buffer` to implement layout
- `BufferLine`: remove wrap from struct, as wrap is passed to layout
- Refactor of scroll and shaping
- Move `color` and `x_opt` out of Cursor
- Add size limit to `font_matches_cache` and clear it when it is reached
- Update `swash` to `0.1.12`
- Set default buffer wrap to `WordOrGlyph`
## Removed
- Remove patch to load Redox system fonts, as fontdb does it now
## [0.10.0] - 2023-10-19
### Added
- Added `Buffer::set_rich_text` method
- Add `Align::End` for end-based alignment
- Add more `Debug` implementations
- Add feature to warn on missing glyphs
- Add easy conversions for tuples/arrays for `Color`
- Derive `Clone` for `AttrsList`
- Add feature to allow `fontdb` to get `fontconfig` information
- Add benchmarks to accurately gauge improvements
- Add image render tests
- Allow BSD-2-Clause and BSD-3-Clause licneses in cargo-deny
### Fixed
- Fix `no_std` build
- Fix `BufferLine::set_align` docs to not mention shape reset is performed
- Fix width computed during unconstrained layout and add test for it
- Set `cursor_moved` to true in `Editor::insert_string`
- Fix `NextWord` action in `Editor` when line ends with word boundaries
- Fix building `editor-libcosmic` with `vi` feature
- Respect `fontconfig` font aliases when enabled
- Fix rendering of RTL words
### Changed
- Unify `no_std` and `std` impls of `FontSystem`
- Move `hashbrown` behind `no_std` feature
- Require either `std` or `no_std` feature to be specified
- Use a scratch buffer to reduce allocations
- Enable `std` feature with `fontconfig` feature
- Enable `fontconfig` feature by default
- Refactor code in `ShapeLine::layout`
- Set MSRV to `1.65`
- Make `Edit::copy_selection` immutable
- Rewrite `PreviousWord` logic in `Editor` with iterators
- Use attributes at cursor position for insertions in `Editor`
- Update all dependencies
- Use `self_cell` for creating self-referential struct
## [0.9.0] - 2023-07-06
### Added
- Add `Shaping` enum to allow selecting the shaping strategy
- Add `Buffer::new_empty` to create `Buffer` without `FontSystem`
- Add `BidiParagraphs` iterator
- Allow setting `Cursor` color
- Allow setting `Editor` cursor
- Add `PhysicalGlyph` that allows computing `CacheKey` after layout
- Add light syntax highlighter to `libcosmic` example
### Fixed
- Fix WebAssembly support
- Fix alignment when not wrapping
- Fallback to monospaced font if Monospace family is not found
- Align glyphs in a `LayoutRun` to baseline
### Changed
- Update `fontdb` to 0.14.1
- Replace ouroboros with aliasable
- Use `BidiParagraphs` iterator instead of `str::Lines`
- Update `libcosmic` version
### Removed
- `LayoutGlyph` no longer has `x_int` and `y_int`, use `PhysicalGlyph` instead
## [0.8.0] - 2023-04-03
### Added
- `FontSystem::new_with_fonts` helper
- Alignment and justification
- `FontSystem::db_mut` provides mutable access to `fontdb` database
- `rustybuzz` is re-exported
### Fixed
- Fix some divide by zero panics
- Redox now uses `std` `FontSystem`
- Layout system improvements
- `BufferLinke::set_text` has been made more efficient
- Fix potential panic on window resize
### Changed
- Use `f32` instead of `i32` for lengths
- `FontSystem` no longer self-referencing
- `SwashCash` no longer keeps reference to `FontSystem`
### Removed
- `Attrs::monospaced` is removed, use `Family::Monospace` instead

182
vendor/cosmic-text/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,182 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.75"
name = "cosmic-text"
version = "0.13.2"
authors = ["Jeremy Soller <jeremy@system76.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Pure Rust multi-line text handling"
documentation = "https://docs.rs/cosmic-text/latest/cosmic_text/"
readme = "README.md"
license = "MIT OR Apache-2.0"
repository = "https://github.com/pop-os/cosmic-text"
[package.metadata.docs.rs]
features = ["vi"]
[profile.test]
opt-level = 1
[lib]
name = "cosmic_text"
path = "src/lib.rs"
[[test]]
name = "editor_modified_state"
path = "tests/editor_modified_state.rs"
[[test]]
name = "shaping_and_rendering"
path = "tests/shaping_and_rendering.rs"
[[test]]
name = "wrap_stability"
path = "tests/wrap_stability.rs"
[[test]]
name = "wrap_word_fallback"
path = "tests/wrap_word_fallback.rs"
[[bench]]
name = "layout"
path = "benches/layout.rs"
harness = false
[dependencies.bitflags]
version = "2.4.1"
[dependencies.cosmic_undo_2]
version = "0.2.0"
optional = true
[dependencies.fontdb]
version = "0.16"
default-features = false
[dependencies.hashbrown]
version = "0.14.1"
optional = true
default-features = false
[dependencies.libm]
version = "0.2.8"
optional = true
[dependencies.log]
version = "0.4.20"
[dependencies.modit]
version = "0.1.4"
optional = true
[dependencies.rangemap]
version = "1.4.0"
[dependencies.rustc-hash]
version = "1.1.0"
default-features = false
[dependencies.rustybuzz]
version = "0.14"
features = ["libm"]
default-features = false
[dependencies.self_cell]
version = "1.0.1"
[dependencies.smol_str]
version = "0.2.2"
default-features = false
[dependencies.swash]
version = "0.2.0"
features = [
"render",
"scale",
]
optional = true
default-features = false
[dependencies.syntect]
version = "5.1.0"
optional = true
[dependencies.sys-locale]
version = "0.3.1"
optional = true
[dependencies.ttf-parser]
version = "0.21"
default-features = false
[dependencies.unicode-bidi]
version = "0.3.13"
features = ["hardcoded-data"]
default-features = false
[dependencies.unicode-linebreak]
version = "0.1.5"
[dependencies.unicode-script]
version = "0.5.5"
[dependencies.unicode-segmentation]
version = "1.10.1"
[dev-dependencies.criterion]
version = "0.5.1"
features = ["cargo_bench_support"]
default-features = false
[dev-dependencies.tiny-skia]
version = "0.11.2"
[features]
default = [
"std",
"swash",
"fontconfig",
]
fontconfig = [
"fontdb/fontconfig",
"std",
]
monospace_fallback = []
no_std = [
"rustybuzz/libm",
"hashbrown",
"dep:libm",
]
shape-run-cache = []
std = [
"fontdb/memmap",
"fontdb/std",
"rustybuzz/std",
"swash?/std",
"sys-locale",
"ttf-parser/std",
"unicode-bidi/std",
]
vi = [
"modit",
"syntect",
"cosmic_undo_2",
]
warn_on_missing_glyphs = []
wasm-web = ["sys-locale?/js"]

201
vendor/cosmic-text/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
vendor/cosmic-text/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2022 System76
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

94
vendor/cosmic-text/README.md vendored Normal file
View File

@@ -0,0 +1,94 @@
# COSMIC Text
[![crates.io](https://img.shields.io/crates/v/cosmic-text.svg)](https://crates.io/crates/cosmic-text)
[![docs.rs](https://docs.rs/cosmic-text/badge.svg)](https://docs.rs/cosmic-text)
![license](https://img.shields.io/crates/l/cosmic-text.svg)
[![Rust workflow](https://github.com/pop-os/cosmic-text/workflows/Rust/badge.svg?event=push)](https://github.com/pop-os/cosmic-text/actions)
Pure Rust multi-line text handling.
COSMIC Text provides advanced text shaping, layout, and rendering wrapped up
into a simple abstraction. Shaping is provided by rustybuzz, and supports a
wide variety of advanced shaping operations. Rendering is provided by swash,
which supports ligatures and color emoji. Layout is implemented custom, in safe
Rust, and supports bidirectional text. Font fallback is also a custom
implementation, reusing some of the static fallback lists in browsers such as
Chromium and Firefox. Linux, macOS, and Windows are supported with the full
feature set. Other platforms may need to implement font fallback capabilities.
## Screenshots
Arabic translation of Universal Declaration of Human Rights
[![Arabic screenshot](screenshots/arabic.png)](screenshots/arabic.png)
Hindi translation of Universal Declaration of Human Rights
[![Hindi screenshot](screenshots/hindi.png)](screenshots/hindi.png)
Simplified Chinese translation of Universal Declaration of Human Rights
[![Simplified Chinses screenshot](screenshots/chinese-simplified.png)](screenshots/chinese-simplified.png)
## Roadmap
The following features must be supported before this is "ready":
- [x] Font loading (using fontdb)
- [x] Preset fonts
- [x] System fonts
- [x] Text styles (bold, italic, etc.)
- [x] Per-buffer
- [x] Per-span
- [x] Font shaping (using rustybuzz)
- [x] Cache results
- [x] RTL
- [x] Bidirectional rendering
- [x] Font fallback
- [x] Choose font based on locale to work around "unification"
- [x] Per-line granularity
- [x] Per-character granularity
- [x] Font layout
- [x] Click detection
- [x] Simple wrapping
- [ ] Wrapping with indentation
- [ ] No wrapping
- [ ] Ellipsize
- [x] Font rendering (using swash)
- [x] Cache results
- [x] Font hinting
- [x] Ligatures
- [x] Color emoji
- [x] Text editing
- [x] Performance improvements
- [x] Text selection
- [x] Can automatically recreate https://unicode.org/udhr/ without errors (see below)
- [x] Bidirectional selection
- [ ] Copy/paste
- [x] no_std support (with `default-features = false`)
- [ ] no_std font loading
- [x] no_std shaping
- [x] no_std layout
- [ ] no_std rendering
The UDHR (Universal Declaration of Human Rights) test involves taking the entire
set of UDHR translations (almost 500 languages), concatenating them as one file
(which ends up being 8 megabytes!), then via the `editor-test` example,
automatically simulating the entry of that file into cosmic-text per-character,
with the use of backspace and delete tested per character and per line. Then,
the final contents of the buffer is compared to the original file. All of the
106746 lines are correct.
## License
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

256
vendor/cosmic-text/benches/layout.rs vendored Normal file
View File

@@ -0,0 +1,256 @@
use cosmic_text as ct;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn load_font_system(c: &mut Criterion) {
c.bench_function("load FontSystem", |b| {
b.iter(|| black_box(ct::FontSystem::new()))
});
}
fn layout(c: &mut Criterion) {
let mut fs = ct::FontSystem::new();
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(10.0, 10.0));
buffer.set_size(&mut fs, Some(80.0), None);
for (wrap_name, wrap) in &[
("None", ct::Wrap::None),
("Glyph", ct::Wrap::Glyph),
("Word", ct::Wrap::Word),
] {
for (shape_name, shape) in &[
("Simple", ct::Shaping::Basic),
("Advanced", ct::Shaping::Advanced),
] {
let mut group = c.benchmark_group(format!("Wrap({wrap_name}, {shape_name})"));
buffer.set_wrap(&mut fs, *wrap);
let mut run_on_text = |text: &str| {
buffer.lines.clear();
buffer.set_text(&mut fs, text, ct::Attrs::new(), *shape);
buffer.shape_until_scroll(&mut fs, false);
};
group.bench_function("small amount of text", |b| {
b.iter(|| {
run_on_text("Hello, world!");
});
});
group.bench_function("large amount of text", |b| {
b.iter(|| {
run_on_text(FIRST_CHAPTER_OF_MOBY_DICK);
});
});
group.bench_function("arabic text", |b| {
b.iter(|| {
run_on_text(include_str!("../sample/arabic.txt"));
})
});
// Reduce the sample count for these next ones.
// If we can optimize the layout for these, remove this line.
group.sample_size(10);
group.bench_function("hebrew text", |b| {
b.iter(|| {
run_on_text(include_str!("../sample/hebrew.txt"));
})
});
group.bench_function("emoji text", |b| {
b.iter(|| {
run_on_text(include_str!("../sample/emoji.txt"));
})
});
}
}
}
criterion_group!(benches, layout, load_font_system);
criterion_main!(benches);
const FIRST_CHAPTER_OF_MOBY_DICK: &str = r#"
Call me Ishmael. Some years ago- never mind how long precisely- having little
or no money in my purse, and nothing particular to interest me on shore, I
thought I would sail about a little and see the watery part of the world. It is
a way I have of driving off the spleen and regulating the circulation. Whenever
I find myself growing grim about the mouth; whenever it is a damp, drizzly
November in my soul; whenever I find myself involuntarily pausing before coffin
warehouses, and bringing up the rear of every funeral I meet; and especially
whenever my hypos get such an upper hand of me, that it requires a strong moral
principle to prevent me from deliberately stepping into the street, and
methodically knocking peoples hats off- then, I account it high time to get
to sea as soon as I can. This is my substitute for pistol and ball. With a
philosophical flourish Cato throws himself upon his sword; I quietly take to
the ship. There is nothing surprising in this. If they but knew it, almost all
men in their degree, some time or other, cherish very nearly the same feelings
towards the ocean with me.
There now is your insular city of the Manhattoes, belted round by wharves as
Indian isles by coral reefs- commerce surrounds it with her surf. Right and
left, the streets take you waterward. Its extreme downtown is the battery,
where that noble mole is washed by waves, and cooled by breezes, which a few
hours previous were out of sight of land. Look at the crowds of water-gazers
there.
Circumambulate the city of a dreamy Sabbath afternoon. Go from Corlears Hook to
Coenties Slip, and from thence, by Whitehall, northward. What do you see?-
Posted like silent sentinels all around the town, stand thousands upon
thousands of mortal men fixed in ocean reveries. Some leaning against the
spiles; some seated upon the pier-heads; some looking over the bulwarks of
ships from China; some high aloft in the rigging, as if striving to get a still
better seaward peep. But these are all landsmen; of week days pent up in lath
and plaster- tied to counters, nailed to benches, clinched to desks. How then
is this? Are the green fields gone? What do they here?
But look! here come more crowds, pacing straight for the water, and seemingly
bound for a dive. Strange! Nothing will content them but the extremest limit of
the land; loitering under the shady lee of yonder warehouses will not suffice.
No. They must get just as nigh the water as they possibly can without falling
And there they stand- miles of them- leagues. Inlanders all, they come from
lanes and alleys, streets avenues- north, east, south, and west. Yet here they
all unite. Tell me, does the magnetic virtue of the needles of the compasses of
all those ships attract them thither?
Once more. Say you are in the country; in some high land of lakes. Take almost
any path you please, and ten to one it carries you down in a dale, and leaves
you there by a pool in the stream. There is magic in it. Let the most
absent-minded of men be plunged in his deepest reveries- stand that man on his
legs, set his feet a-going, and he will infallibly lead you to water, if water
there be in all that region. Should you ever be athirst in the great American
desert, try this experiment, if your caravan happen to be supplied with a
metaphysical professor. Yes, as every one knows, meditation and water are
wedded for ever.
But here is an artist. He desires to paint you the dreamiest, shadiest,
quietest, most enchanting bit of romantic landscape in all the valley of the
Saco. What is the chief element he employs? There stand his trees, each with a
hollow trunk, as if a hermit and a crucifix were within; and here sleeps his
meadow, and there sleep his cattle; and up from yonder cottage goes a sleepy
smoke. Deep into distant woodlands winds a mazy way, reaching to overlapping
spurs of mountains bathed in their hill-side blue. But though the picture lies
thus tranced, and though this pine-tree shakes down its sighs like leaves upon
this shepherds head, yet all were vain, unless the shepherds eye were
fixed upon the magic stream before him. Go visit the Prairies in June, when for
scores on scores of miles you wade knee-deep among Tiger-lilies- what is the
one charm wanting?- Water- there is not a drop of water there! Were Niagara but
a cataract of sand, would you travel your thousand miles to see it? Why did the
poor poet of Tennessee, upon suddenly receiving two handfuls of silver,
deliberate whether to buy him a coat, which he sadly needed, or invest his
money in a pedestrian trip to Rockaway Beach? Why is almost every robust
healthy boy with a robust healthy soul in him, at some time or other crazy to
go to sea? Why upon your first voyage as a passenger, did you yourself feel
such a mystical vibration, when first told that you and your ship were now out
of sight of land? Why did the old Persians hold the sea holy? Why did the
Greeks give it a separate deity, and own brother of Jove? Surely all this is
not without meaning. And still deeper the meaning of that story of Narcissus,
who because he could not grasp the tormenting, mild image he saw in the
fountain, plunged into it and was drowned. But that same image, we ourselves
see in all rivers and oceans. It is the image of the ungraspable phantom of
life; and this is the key to it all.
Now, when I say that I am in the habit of going to sea whenever I begin to grow
hazy about the eyes, and begin to be over conscious of my lungs, I do not mean
to have it inferred that I ever go to sea as a passenger. For to go as a
passenger you must needs have a purse, and a purse is but a rag unless you have
something in it. Besides, passengers get sea-sick- grow quarrelsome- dont
sleep of nights- do not enjoy themselves much, as a general thing;- no, I never
go as a passenger; nor, though I am something of a salt, do I ever go to sea as
a Commodore, or a Captain, or a Cook. I abandon the glory and distinction of
such offices to those who like them. For my part, I abominate all honorable
respectable toils, trials, and tribulations of every kind whatsoever. It is
quite as much as I can do to take care of myself, without taking care of ships,
barques, brigs, schooners, and what not. And as for going as cook,- though I
confess there is considerable glory in that, a cook being a sort of officer on
ship-board- yet, somehow, I never fancied broiling fowls;- though once broiled,
judiciously buttered, and judgmatically salted and peppered, there is no one
who will speak more respectfully, not to say reverentially, of a broiled fowl
than I will. It is out of the idolatrous dotings of the old Egyptians upon
broiled ibis and roasted river horse, that you see the mummies of those
creatures in their huge bakehouses the pyramids.
No, when I go to sea, I go as a simple sailor, right before the mast, plumb
down into the fore-castle, aloft there to the royal mast-head. True, they
rather order me about some, and make me jump from spar to spar, like a
grasshopper in a May meadow. And at first, this sort of thing is unpleasant
enough. It touches ones sense of honor, particularly if you come of an old
established family in the land, the Van Rensselaers, or Randolphs, or
Hardicanutes. And more than all, if just previous to putting your hand into the
tar-pot, you have been lording it as a country schoolmaster, making the tallest
boys stand in awe of you. The transition is a keen one, I assure you, from a
schoolmaster to a sailor, and requires a strong decoction of Seneca and the
Stoics to enable you to grin and bear it. But even this wears off in time.
What of it, if some old hunks of a sea-captain orders me to get a broom and
sweep down the decks? What does that indignity amount to, weighed, I mean, in
the scales of the New Testament? Do you think the archangel Gabriel thinks
anything the less of me, because I promptly and respectfully obey that old
hunks in that particular instance? Who aint a slave? Tell me that. Well,
then, however the old sea-captains may order me about- however they may thump
and punch me about, I have the satisfaction of knowing that it is all right;
that everybody else is one way or other served in much the same way- either in
a physical or metaphysical point of view, that is; and so the universal thump
is passed round, and all hands should rub each others shoulder-blades, and
be content.
Again, I always go to sea as a sailor, because they make a point of paying me
for my trouble, whereas they never pay passengers a single penny that I ever
heard of. On the contrary, passengers themselves must pay. And there is all the
difference in the world between paying and being paid. The act of paying is
perhaps the most uncomfortable infliction that the two orchard thieves entailed
upon us. But being paid,- what will compare with it? The urbane activity with
which a man receives money is really marvellous, considering that we so
earnestly believe money to be the root of all earthly ills, and that on no
account can a monied man enter heaven. Ah! how cheerfully we consign ourselves
to perdition!
Finally, I always go to sea as a sailor, because of the wholesome exercise and
pure air of the fore-castle deck. For as in this world, head winds are far more
prevalent than winds from astern (that is, if you never violate the Pythagorean
maxim), so for the most part the Commodore on the quarter-deck gets his
atmosphere at second hand from the sailors on the forecastle. He thinks he
breathes it first; but not so. In much the same way do the commonalty lead
their leaders in many other things, at the same time that the leaders little
suspect it. But wherefore it was that after having repeatedly smelt the sea as
a merchant sailor, I should now take it into my head to go on a whaling voyage;
this the invisible police officer of the Fates, who has the constant
surveillance of me, and secretly dogs me, and influences me in some
unaccountable way- he can better answer than any one else. And, doubtless, my
going on this whaling voyage, formed part of the grand programme of Providence
that was drawn up a long time ago. It came in as a sort of brief interlude and
solo between more extensive performances. I take it that this part of the bill
must have run something like this:
“Grand Contested Election for the Presidency of the United States.“WHALING
VOYAGE BY ONE ISHMAEL.” “BLOODY BATTLE IN AFFGHANISTAN.”
Though I cannot tell why it was exactly that those stage managers, the Fates,
put me down for this shabby part of a whaling voyage, when others were set down
for magnificent parts in high tragedies, and short and easy parts in genteel
comedies, and jolly parts in farces- though I cannot tell why this was exactly;
yet, now that I recall all the circumstances, I think I can see a little into
the springs and motives which being cunningly presented to me under various
disguises, induced me to set about performing the part I did, besides cajoling
me into the delusion that it was a choice resulting from my own unbiased
freewill and discriminating judgment.
Chief among these motives was the overwhelming idea of the great whale himself.
Such a portentous and mysterious monster roused all my curiosity. Then the wild
and distant seas where he rolled his island bulk; the undeliverable, nameless
perils of the whale; these, with all the attending marvels of a thousand
Patagonian sights and sounds, helped to sway me to my wish. With other men,
perhaps, such things would not have been inducements; but as for me, I am
tormented with an everlasting itch for things remote. I love to sail forbidden
seas, and land on barbarous coasts. Not ignoring what is good, I am quick to
perceive a horror, and could still be social with it- would they let me- since
it is but well to be on friendly terms with all the inmates of the place one
lodges in.
By reason of these things, then, the whaling voyage was welcome; the great
flood-gates of the wonder-world swung open, and in the wild conceits that
swayed me to my purpose, two and two there floated into my inmost soul, endless
processions of the whale, and, mid most of them all, one grand hooded phantom,
like a snow hill in the air.
"#;

39
vendor/cosmic-text/ci.sh vendored Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
function build {
cargo build --release "$@"
cargo clippy --no-deps "$@"
}
set -ex
echo Check formatting
cargo fmt --check
echo Build with default features
build
echo Install target for no_std build
# This is necessary because Rust otherwise may silently use std regardless.
rustup target add thumbv8m.main-none-eabihf
echo Build with only no_std feature
build --no-default-features --features no_std --target thumbv8m.main-none-eabihf
echo Build with only std feature
build --no-default-features --features std
echo Build with only std and swash features
build --no-default-features --features std,swash
echo Build with only std and syntect features
build --no-default-features --features std,syntect
echo Build with only std and vi features
build --no-default-features --features std,vi
echo Build with all features
build --all-features
echo Run tests
cargo test

265
vendor/cosmic-text/deny.toml vendored Normal file
View File

@@ -0,0 +1,265 @@
# This template contains all of the possible sections and their default values
# Note that all fields that take a lint level have these possible values:
# * deny - An error will be produced and the check will fail
# * warn - A warning will be produced, but the check will not fail
# * allow - No warning or error will be produced, though in some cases a note
# will be
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
# Root options
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
# dependency, such as, for example, the `nix` crate only being used via the
# `target_family = "unix"` configuration, that only having windows targets in
# this list would mean the nix crate, as well as any of its exclusive
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# When creating the dependency graph used as the source of truth when checks are
# executed, this field can be used to prune crates from the graph, removing them
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
# is pruned from the graph, all of its dependencies will also be pruned unless
# they are connected to another crate in the graph that hasn't been pruned,
# so it should be used with care. The identifiers are [Package ID Specifications]
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
#exclude = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = true
# If set, these feature will be enabled when collecting metadata. If `--features`
# is specified on the cmd line they will take precedence over this option.
#features = []
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory database is cloned/fetched into
db-path = "~/.cargo/advisory-db"
# The url(s) of the advisory databases to use
db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
unmaintained = "warn"
# The lint level for crates that have been yanked from their source registry
yanked = "warn"
# The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db
notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = []
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
# will still output a note when they are encountered.
# * None - CVSS Score 0.0
# * Low - CVSS Score 0.1 - 3.9
# * Medium - CVSS Score 4.0 - 6.9
# * High - CVSS Score 7.0 - 8.9
# * Critical - CVSS Score 9.0 - 10.0
#severity-threshold =
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# See Git Authentication for more information about setting up git authentication.
#git-fetch-with-cli = true
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
# The lint level for crates which do not have a detectable license
unlicensed = "deny"
# List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
"MIT",
"Apache-2.0",
"Unicode-DFS-2016",
"BSD-3-Clause",
"BSD-2-Clause",
"Zlib",
#"Apache-2.0 WITH LLVM-exception",
]
# List of explicitly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
deny = [
#"Nokia",
]
# Lint level for licenses considered copyleft
copyleft = "warn"
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
# * both - The license will be approved if it is both OSI-approved *AND* FSF
# * either - The license will be approved if it is either OSI-approved *OR* FSF
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
# * neither - This predicate is ignored and the default lint level is used
allow-osi-fsf-free = "neither"
# Lint level used when no other predicates are matched
# 1. License isn't in the allow or deny lists
# 2. License isn't copyleft
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
default = "deny"
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
# [possible values: any between 0.0 and 1.0].
confidence-threshold = 0.8
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], name = "adler32", version = "*" },
]
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
#[[licenses.clarify]]
# The name of the crate the clarification applies to
#name = "ring"
# The optional version constraint for the crate
#version = "*"
# The SPDX expression for the license requirements of the crate
#expression = "MIT AND ISC AND OpenSSL"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries.
# To see how to mark a crate as unpublished (to the official registry),
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
ignore = false
# One or more private registries that you might publish crates to, if a crate
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
]
# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "deny"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
# with multiple versions
# * lowest-version - The path to the lowest versioned duplicate is highlighted
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overriden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overriden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care!
allow = [
#{ name = "ansi_term", version = "=0.11.0" },
]
# List of crates to deny
deny = [
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#{ name = "ansi_term", version = "=0.11.0" },
#
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
]
# List of features to allow/deny
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#[[bans.features]]
#name = "reqwest"
# Features to not allow
#deny = ["json"]
# Features to allow
#allow = [
# "rustls",
# "__rustls",
# "__tls",
# "hyper-rustls",
# "rustls",
# "rustls-pemfile",
# "rustls-tls-webpki-roots",
# "tokio-rustls",
# "webpki-roots",
#]
# If true, the allowed features must exactly match the enabled feature set. If
# this is set there is no point setting `deny`
#exact = true
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
{ name = "bitflags" },
{ name = "syn" },
#{ name = "ansi_term", version = "=0.11.0" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite.
skip-tree = [
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
# More documentation about the 'sources' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
[sources]
# Lint level for what to happen when a crate from a crate registry that is not
# in the allow list is encountered
unknown-registry = "warn"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = []

3
vendor/cosmic-text/editor-test.sh vendored Executable file
View File

@@ -0,0 +1,3 @@
# SPDX-License-Identifier: MIT OR Apache-2.0
RUST_LOG="cosmic_text=debug,editor_test=debug" cargo run --release --package editor-test -- "$@"

3
vendor/cosmic-text/editor.sh vendored Executable file
View File

@@ -0,0 +1,3 @@
# SPDX-License-Identifier: MIT OR Apache-2.0
RUST_LOG="cosmic_text=debug,editor=debug" cargo run --release --package editor -- "$@"

View File

@@ -0,0 +1,93 @@
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

93
vendor/cosmic-text/fonts/Inter-LICENSE vendored Normal file
View File

@@ -0,0 +1,93 @@
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View File

@@ -0,0 +1,93 @@
Copyright 2012 Google Inc. All Rights Reserved.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
vendor/cosmic-text/multiview.sh vendored Executable file
View File

@@ -0,0 +1,3 @@
# SPDX-License-Identifier: MIT OR Apache-2.0
RUST_LOG="cosmic_text=debug,multiview=debug" cargo run --release --package multiview -- "$@"

27
vendor/cosmic-text/redoxer.sh vendored Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -ex
rm -rf target/redoxer
mkdir -p target/redoxer
redoxer install \
--no-track \
--path examples/editor-orbclient \
--root "target/redoxer"
args=(env RUST_LOG=cosmic_text=debug,editor_orbclient=debug /root/bin/editor-orbclient)
if [ -f "$1" ]
then
filename="$(basename "$1")"
cp "$1" "target/redoxer/${filename}"
args+=("${filename}")
fi
cd target/redoxer
# TODO: remove need for linking fonts
redoxer exec \
--gui \
--folder . \
"${args[@]}"

1
vendor/cosmic-text/rich-text.sh vendored Executable file
View File

@@ -0,0 +1 @@
RUST_LOG=cosmic_text=debug,rich_text=debug cargo run --release --package rich-text -- "$@"

3
vendor/cosmic-text/sample/arabic.txt vendored Normal file
View File

@@ -0,0 +1,3 @@
I like to render اللغة العربية in Rust!
عندما يريد العالم أن ‪يتكلّم ، فهو يتحدّث بلغة يونيكود. تسجّل الآن لحضور المؤتمر الدولي العاشر ليونيكود (Unicode Conference)، الذي سيعقد في 10-12 آذار 1997 بمدينة مَايِنْتْس، ألمانيا. و سيجمع المؤتمر بين خبراء من كافة قطاعات الصناعة على الشبكة العالمية انترنيت ويونيكود، حيث ستتم، على الصعيدين الدولي والمحلي على حد سواء مناقشة سبل استخدام يونكود في النظم القائمة وفيما يخص التطبيقات الحاسوبية، الخطوط، تصميم النصوص والحوسبة متعددة اللغات.

1
vendor/cosmic-text/sample/bengali.txt vendored Normal file
View File

@@ -0,0 +1 @@
ধারা ১ সমস্ত মানুষ স্বাধীনভাবে সমান মর্যাদা এবং অধিকার নিয়ে জন্মগ্রহণ করে। তাঁদের বিবেক এবং বুদ্ধি আছে; সুতরাং সকলেরই একে অপরের প্রতি ভ্রাতৃত্বসুলভ মনোভাব নিয়ে আচরণ করা উচিত।

2
vendor/cosmic-text/sample/crlf.txt vendored Normal file
View File

@@ -0,0 +1,2 @@
These are two lines
in a CRLF file

4
vendor/cosmic-text/sample/em.txt vendored Normal file
View File

@@ -0,0 +1,4 @@
— — — — — — — —
 — — — — — — —
— — — — — — — —
 — — — — — — —

View File

@@ -0,0 +1 @@
I want more terminals to be able to handle ZWJ sequence emoji characters. For example, the service dog emoji 🐕‍🦺 is actually 3 Unicode characters. Kitty handles this fairly well. All VTE-based terminals, however, show "🐶🦺".

3793
vendor/cosmic-text/sample/emoji.txt vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
TE🤯STالعربيةTE🤣ST你好
العربيةTE🤯STالعربيةTE🤣ST你好العربية

11
vendor/cosmic-text/sample/farsi.txt vendored Normal file
View File

@@ -0,0 +1,11 @@
من از سیستم عامل پاپ! ۲۲.۰۴ استفاده میکنم.
متن راست به چپ Left to right text ادامه‌ی متن راست به چپ با اعداد ۱/۲۳۴ و English numbers 1.234.
مَتنِ فارسی و انگلیسی (English).
کشیده نویسی (کشــــــــــــیده‌نویسی).
علائم تُ تِ تَ تً تٍ تْ تٌ.
لام.
ویرگول (؛).
تای تأنیث یا ه دو نقطه (ة).
علامت تشدید (ــّـ).
Testing LEFTTORIGHT ISOLATE (U+2066) and POP DIRECTIONAL ISOLATE (U+2069):
He said: "بهتره از Rust استفاده کنی".

29
vendor/cosmic-text/sample/han.txt vendored Normal file
View File

@@ -0,0 +1,29 @@
https://en.wikipedia.org/wiki/Han_unification#Examples_of_language-dependent_glyphs

3
vendor/cosmic-text/sample/hebrew.txt vendored Normal file
View File

@@ -0,0 +1,3 @@
כאשר העולם רוצה לדבר, הוא מדבר ב־Unicode. הירשמו כעת לכנס Unicode הבינלאומי העשירי, שייערך בין התאריכים 12־10 במרץ 1997, בְּמָיְינְץ שבגרמניה. בכנס ישתתפו מומחים מכל ענפי התעשייה בנושא האינטרנט העולמי וה־Unicode, בהתאמה לשוק הבינלאומי והמקומי, ביישום Unicode במערכות הפעלה וביישומים, בגופנים, בפריסת טקסט ובמחשוב רב־לשוני.
Many computer programs fail to display bidirectional text correctly. For example, this page is mostly LTR English script, and here is the RTL Hebrew name Sarah: שרה, spelled sin (ש) on the right, resh (ר) in the middle, and heh (ה) on the left.

266
vendor/cosmic-text/sample/hello.txt vendored Normal file
View File

@@ -0,0 +1,266 @@
https://en.wiktionary.org/wiki/hello#Translations
Abkhaz: бзиа збаша (bzia zbaša), мыш бзи (məš bzi), (to a man) бзиара убааит (bziara ubaaiṭ), (to a woman) бзиара ббааит (bziara bbaaiṭ), (to more than one person) бзиара жәбааит pl (bziara ž°baaiṭ)
Afrikaans: hallo (af), goeiedag
Ainu: イランカラㇷ゚テ (irankarapte)
Albanian: tungjatjeta (sq), tung (informal), ç'kemi m or f
Arbëreshë Albanian: falem (sq)
Alemannic German: sälü, hoi, hello
Aleut: aang, draas
Ambonese Malay: wai
American Sign Language: B@Sfhead-PalmForward B@FromSfhead-PalmForward
Amharic: ሰላም (sälam)
Apache:
Jicarilla: dá nzhǫ́
Western Apache: dagotʼee, daʼanzho, yaʼateh
Arabic: السَّلَامُ عَلَيْكُمْ‎ (ar) (as-salāmu ʿalaykum), سَلَام‎ (ar) (salām), مَرْحَبًا‎ (ar) (marḥaban), أَهْلًا‎ (ar) (ʾahlan)
Egyptian Arabic: اهلاً‎ (ahlan)
Iraqi Arabic: هلو‎ (helaww)
Archi: салам алейкум (salam alejkum), варчӀами (warčʼami)
Armenian: բարև (hy) (barew), ողջույն (hy) (ołǰuyn)
Assamese: নমস্কাৰ (nomoskar) (very formal), আচ্চেলামো আলাইকোম (asselamü alaiküm) (formal, used among Muslims), হেল’ (helö)
Assyrian Neo-Aramaic: ܫܠܵܡܵܐ‎ (šlama), (to a man) ܫܠܵܡܵܐ ܥܲܠܘܼܟ݂‎ (šlama ʿāloḳ), (to a woman) ܫܠܵܡܵܐ ܥܲܠܵܟ݂ܝ‎ (šlama ʿālaḳ), (to more than one person) ܫܠܵܡܵܐ ܥܲܠܵܘܟ݂ܘܿܢ‎ (šlama ʿāloḳon)
Asturian: hola (ast)
Azerbaijani: salam (az), səlam (South Azerbaijani), hər vaxtınız xeyir olsun, hər vaxtınız xeyir
Bashkir: сәләм (säläm)
Basque: kaixo (eu)
Bats: please add this translation if you can
Bavarian: servus, grias di, pfiati (Timau)
Belarusian: віта́ю (vitáju), здаро́ў (zdaróŭ) (colloquial), до́бры дзень (dóbry dzjenʹ) (good day)
Bengali: নমস্কার (bn) (nômôśkar), আসসালামুআলাইকুম (aśśalamualaikum), সালাম (śalam), হ্যালো (hjalo)
Bhojpuri: प्रणाम (praṇām)
Bouyei: mengz ndil
Bulgarian: здра́сти (bg) (zdrásti) (familiar), здраве́й (bg) sg (zdravéj) (familiar), здраве́йте (bg) pl (zdravéjte) (formal)
Burmese: မင်္ဂလာပါ (my) (mangga.lapa) , ဟဲလို (my) (hai:lui) (colloquial)
Catalan: hola (ca)
Cayuga: sgę́:nǫʔ
Central Atlas Tamazight: ⴰⵣⵓⵍ (azul)
Chamorro: håfa adai
Chechen: маршалла ду хьоьга (maršalla du ḥöga) (to one person), маршалла ду шуьга (maršalla du šüga) (to a group of people), ассаламу ӏалайкум (assalamu ʿalajkum)
Cherokee: ᎣᏏᏲ (chr) (osiyo)
Chichewa: moni
Chickasaw: chokma
Chinese:
Cantonese: 你好 (nei5 hou2), 哈佬 (haa1 lou2)
Dungan: ни хо (ni ho), сэляму (seli͡amu), хома (homa)
Hakka: 你好 (ngì-hó)
Mandarin: 你好 (zh) (nǐ hǎo), 您好 (zh) (nín hǎo) (polite), 你們好, 你们好 (nǐmen hǎo) (to a group of people), 好 (zh) (hǎo) (following an address form or name), 嗨 (zh) (hāi), 哈囉 (zh), 哈啰 (zh) (hāluó)
Min Dong: 汝好 (nṳ̄ hō̤)
Min Nan: 汝好 (lí hó)
Xiang: please add this translation if you can
Wu: 儂好, 侬好 (non hau)
Choctaw: halito
Chukchi: еттык (ettyk) (formal), ети (eti) (informal), етти (etti) (informal)
Coptic: ⲛⲟϥⲣⲓ (nofri)
Cornish: dydh da
Corsican: bonghjornu
Cree:
Plains Cree: tânisi
Czech: ahoj (cs), nazdar (cs) (informal), servus (cs) (informal), dobrý den (cs) (formal)
Danish: hej (da), dav (da), god dag (formal), hallo (da)
Dhivehi: އައްސަލާމު ޢަލައިކުމް‎ (assalāmu ʿalaikum̊)
Dutch: hallo (nl), hoi (nl), hai (nl), hé (nl), dag (nl) (informal), goeiedag (nl), goededag (nl), goedendag (nl), goeiendag (nl) (formal)
Esperanto: saluton (eo)
Estonian: tere (et), hei (et)
Faroese: hey, halló
Fijian: bula (fj)
Finnish: terve (fi), moi (fi), hei (fi), moikka (fi)
French: bonjour (fr), salut (fr) (informal), coucou (fr)(informal), cocorico (fr)
Friulian: mandi
Galician: ola (gl), oula, ouga
Georgian: გამარჯობა (ka) (gamarǯoba), ჰეი (hei)
German: hallo (de), guten Tag (de), servus (de), moin (de), grüß Gott (de) (Southern German, Austria)
Alemannic German: grüezi
Gilbertese: mauri
Gothic: 𐌷𐌰𐌹𐌻𐍃 (hails), 𐌷𐌰𐌹𐌻𐌰 (haila)
Greek: γεια (el) (geia), γεια σου sg (geia sou), γεια σας pl (geia sas), χαίρετε (el) (chaírete)
Ancient: χαῖρε sg (khaîre), χαίρετε pl (khaírete), χαῖρε καί ὑγίαινε sg (khaîre kaí hugíaine)
Greenlandic: aluu (kl)
Guaraní: maitei (gn)
Gujarati: નમસ્તે (namaste), નમસ્કાર (namaskār)
Haitian Creole: bonjou
Hausa: sannu
Hawaiian: aloha
Hebrew: שָׁלוֹם‎ (he) (shalóm), שָׁלוֹם עָלֵיכֶם‎ (he) (shalóm 'aleikhém)
Hindi: नमस्ते (hi) (namaste), नमस्कार (hi) (namaskār), सलाम (hi) (salām) (used by Muslims), सत श्री अकाल (sat śrī akāl) (Sikh, hello/goodbye), हेलो (hi) (helo), हलो (halo), सत्य (hi) (satya), आदाब (hi) (ādāb)
Hmong:
Green Hmong: nyob zoo
White Hmong: nyob zoo
Hungarian: szia (hu), sziasztok (hu) pl (informal), szervusz (hu), szervusztok pl (somewhat formal), heló (hu), helló (hu) (informal), jó napot (hu), jó napot kívánok (hu) (formal), üdvözlöm
Icelandic: halló (is), hæ (is), góðan dag (is), góðan daginn (is)
Ido: hola (io)
Igbo: kèdu
Indonesian: hai (id), salam (id)
Interlingua: bon die, salute (ia)
Irish: Dia dhuit (formal, singular), Dia dhaoibh (formal, plural), Dia's Muire dhuit (formal, singular, response), Dia's Muire dhaoibh (formal, plural, response)
Isan: please add this translation if you can
Italian: ciao (it), salve (it), buongiorno (it), saluti (it) m pl
Iu Mien: yiem longx nyei
Jamaican Creole: ello, wah gwaan
Japanese: おはよう (ja) (ohayō) (morning), こんにちは (ja) (konnichi wa) (daytime), こんばんは (ja) (konban wa) (evening)
Javanese: halo
Jeju: 반갑수다 (ban-gapsuda), 펜안ᄒᆞ우꽈 (pen-anhawukkwa), 펜안 (pen-an)
Judeo-Tat: шолум (şolum)
Kabardian: уузыншэм (wwzənšăm)
Kabyle: azul
Kalmyk: мендвт (mendvt), менд (mend) (informal)
Kannada: ತುಳಿಲು (kn) (tuḷilu), ನಮಸ್ಕಾರ (kn) (namaskāra)
Karachay-Balkar: кюнюгюз ашхы болсун (künügüz aşxı bolsun), ассаламу алейкум (assalamu aleykum)
Karelian: terveh, hei
Kazakh: сәлем (kk) (sälem) (informal), сәлеметсіздер (sälemetsızder) (formal)
Khmer: ជំរាបសួរ (cumriəp suə), សួស្តី (suəsdəy)
Khün: please add this translation if you can
Kinyarwanda: muraho
Korean: 안녕하십니까 (annyeonghasimnikka) (formal), 안녕하세요 (ko) (annyeonghaseyo) (neutrally formal), 안녕(安寧) (ko) (annyeong) (informal)
Krio: kushɛ
Kurdish:
Northern Kurdish: merheba (ku), silav (ku), selam (ku)
Kyrgyz: саламатсыздарбы (salamatsızdarbı), салам (ky) (salam)
Ladino: shalom, bonjur, buenos diyas
Lak: салам (salam)
Lakota: háu
Lao: ສະບາຍດີ (sa bāi dī)
Latin: salvē (la) sg, salvēte (la) pl; avē (la) sg, avēte pl
Latvian: sveiki (informal to more than one person or people of indeterminate gender), sveiks (to a man), sveika (to a woman), čau (informal)
Laz: გეგაჯგინას (gegaǯginas)
Lezgi: салам (salam)
Lithuanian: labas (lt), sveikas (lt) (informal), sveiki (lt) (formal)
Livonian: tēriņtš
Luo: msawa
Lü: ᦍᦲᧃᦡᦲ (yiinḋii)
Luxembourgish: hallo
Macedonian: здраво (zdravo)
Malagasy: manao ahoana? (mg), salama (mg) (Tsimihety)
Malay: helo (ms), apa khabar (ms), salam (ms)
Malayalam: ഹലോ (halō), നമസ്തേ (ml) (namastē), നമസ്കാരം (ml) (namaskāraṃ)
Maltese: bonġu (mt) (before noon), bonswa (after noon), nsellimlek (formal one to one person), nsellmilkom (formal one to more than one person), nsellmulek (formal more than one person to one person), nsellmulkom (formal more than one person to more than one persons)
Manchu: ᠰᠠᡳᠶᡡᠨ (saiyūn)
Maori: kia ora (mi) (informal), tēnā koe (formal to one person), tēnā kōrua (formal to two people), tēnā koutou (formal to three or more people)
Mapudungun: mari mari
Maranungku: yo
Marathi: नमस्कार (mr) (namaskār)
Michif: tánishi, boñjour
Mingrelian: გომორძგუა (gomorʒgua)
Mohawk: sekoh
Mongolian:
Cyrillic: сайн уу? (mn) (sayn uu?) (informal), сайн байна уу? (mn) (sayn bayna uu?)
Mopan Maya: dʼyoos
Nahuatl: niltze (nah), panoltih
Navajo: yáʼátʼééh
Neapolitan: uè
Nepali: नमस्ते (ne) (namaste), नमस्कार (ne) (namaskār)
Norman: baon-n-jour (Guernsey), banjour (Guernsey), boujouo (continental Normandy), bouônjour (Jersey), bwõju (Sark)
Northern Thai: สบายดีก่อ
Norwegian:
Bokmål: hallo (no), hei (no), god dag (no) (formal), halla (no) (informal), heisann
Ojibwe: boozhoo
Okinawan: はいさい m (haisai), はいたい f (haitai), はい n (hai)
Old English: wes hāl
Oriya: ନମସ୍କାର (or) (nômôskarô)
Ossetian: салам (salam), байрай (bajraj), арфӕ (arfæ)
Palauan: alii
Pashto: سلام‎ (ps) (salām), سلام الېک‎ (slāmālék), السلام عليکم‎ (as-salám alaykúm)
Persian: سلام‎ (fa) (salâm), سلام علیکم‎ (salâmo alaykom) (religious), درود‎ (fa) (dorud) (literary)
Pitcairn-Norfolk: watawieh
Polish: cześć (pl) (informal), witaj (pl), witajcie, witam (more formal), dzień dobry (pl) (formal), siema (pl) (informal), halo (pl) (on phone), serwus (pl) (colloquial), cześka (colloquial), siemanero (colloquial)
Portuguese: oi (pt), olá (pt), (slang) e aí? (pt)
Punjabi: ਸਤਿ ਸ਼੍ਰੀ ਅਕਾਲ (sati śrī akāl)
Rapa Nui: 'iorana
Romani: te aves baxtalo (to a male), te aves baxtali (to a female), te aven baxtale (to two or more people)
Romanian: salut (ro), bună (ro), noroc (ro) (informal), bună ziua (formal), servus (ro)
Russian: приве́т (ru) (privét) (informal), здоро́во (ru) (zdoróvo) (colloquial), здра́вствуйте (ru) (zdrávstvujte) (formal, first "в" is silent), до́брый день (ru) (dóbryj denʹ), здра́вствуй (ru) (zdrávstvuj) (informal, first "в" is silent), салю́т (ru) (saljút)
Rusyn: наздар (nazdar)
Sami:
Inari Sami: tiervâ
Northern: dearvva, būres
Skolt: tiõrv
Southern: buaregh
Samoan: talofa
Sanskrit: नमस्कार (sa) (namaskāra), नमस्ते (namaste), नमो नमः (namo namaḥ) (formal)
Scots: hullo
Scottish Gaelic: halò (informal), latha math (formal), (informal) hòigh
Serbo-Croatian:
Cyrillic: здра̏во, ћа̑о, ме̏рха̄ба, селам, бог, бок
Roman: zdrȁvo (sh), ćȃo (sh), mȅrhāba, selam (sh), bog, bok (sh)
Sesotho: lumela
Shan: please add this translation if you can
Shona: mhoro
Sichuan Yi: please add this translation if you can
Sicilian: ciao, salutamu
Sindhi: هيلو‎
Sinhalese: හලෝ (halō), ආයුබෝවන් (si) (āyubōwan)
Situ: please add this translation if you can
Slovak: ahoj (sk), nazdar (informal), servus (sk) (informal), dobrý deň (formal)
Slovene: žívjo, zdrávo (informal), dóber dán, pozdravljeni (formal)
Somali: ma nabad baa, waa nabad
Sorbian:
Lower Sorbian: dobry źeń
Sotho: dumela (st)
Spanish: hola (es), buenos días (es), qué tal, buenas tardes (es)
Sundanese: halo
Svan: ხოჩა ლადა̈ღ (xoča ladäɣ)
Swahili: jambo (sw), salaam
Swedish: hallå (sv), hej (sv), god dag (sv) (formal), tjena (sv), hejsan (sv) (informal), tja (sv)
Tagalog: kamusta (tl)/kumusta (tl), musta (tl) (slang), hoy (tl), huy, oy/oi (informal), uy/ui (informal)
Tajik: салом (salom)
Tamil: வணக்கம் (ta) (vaṇakkam)
Tangsa: äshazhoix
Tatar: сәлам (tt) (sälam)
Telugu: నమసకారం (namasakāraṁ), బాగున్నారా (bāgunnārā)
Tetum: please add this translation if you can
Thai: สวัสดี (th) (sà-wàt-dii), สวัสดีครับ (male speaker), สวัสดีค่ะ (female speaker), หวัดดี (wàt-dii)
Tibetan: བཀྲ་ཤིས་བདེ་ལེགས (bkra shis bde legs)
Tigrinya: ሰላም (sälam)
Tongan: mālō e lelei
Tswana: dumela (tn) (singular, as in dumela, rra, "hello sir"), dumelang (tn) (plural, as in dumelang, borra, "hello gentlemen")
Turkish: merhaba (tr), selam (tr)
Turkmen: salam
Tuvan: экии (ekii)
Udmurt: ӟеч (dźeć), чырткем (ćyrtkem), умой (umoj)
Ukrainian: приві́т (uk) (pryvít) (informal), здоро́в був (uk) (zdoróv buv) (informal), добри́день (uk) (dobrýdenʹ) (neutral or formal), чоло́м (čolóm)
Urdu: سلام علیکم‎ (salām-o-alaikum), اسلام علیکم‎ (literally “Peace be upon you”), اسلام علیکم ورحمۃاللہ وبرکاتہ‎ (literally “Peace be upon you & May Allah bless”), آداب‎ (ur) (ādāb)
Uyghur: سالام‎ (salam)
Uzbek: salom (uz)
Venetian: ciao (vec)
Vietnamese: xin chào (vi), chào (vi)
Volapük: glidis
Walloon: bondjoû (wa), a (wa), diewåde (wa) (old)
Welsh: helo (cy), bore da (good morning), dydd da (good day), hylo
West Frisian: hallo, hoi
Winnebago: haho (male speaker), hą (female speaker), hinįkaraginʼ
Xhosa: molo sg, molweni pl
Xibe: ᠪᠠᡳᡨᠠᡴᡡ
ᠨᠠ (baitakū na)
Yakut: эҕэрдэ (eğerde), дорообо (doroobo) (informal)
Yiddish: שלום־עליכם‎ (sholem-aleykhem), אַ גוטן‎ (yi) (a gutn), גוט־מאָרגן‎ (yi) (gut-morgn)
Yoruba: Pẹlẹ o
Yup'ik: waqaa, cama-i
Zapotec: padiull
Zazaki: sılam, namaste
Zhuang: mwngz ndei
Zulu: sawubona (zu) (familiar), sanibonani (plural, respectful)

View File

@@ -0,0 +1,6 @@
fi ffi 🐕‍🦺 fi ffi
fi تما 🐕‍🦺 ffi تما
ffi fi 🐕‍🦺 ffi fi
تما تما 🐕‍🦺 تما
تما ffi 🐕‍🦺 تما fi تما
تما تما 🐕‍🦺 تما

View File

@@ -0,0 +1,11 @@
《施氏食狮史》
石室诗士施氏,嗜狮,誓食十狮。
氏时时适市视狮。
十时,适十狮适市。
是时,适施氏适市。
氏视是十狮,恃矢势,使是十狮逝世。
氏拾是十狮尸,适石室。
石室湿,氏使侍拭石室。
石室拭,氏始试食是十狮。
食时,始识是十狮尸,实十石狮尸。
试释是事。

213
vendor/cosmic-text/sample/mono.txt vendored Normal file
View File

@@ -0,0 +1,213 @@
https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-demo.txt
UTF-8 encoded sample plain-text file
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Markus Kuhn [ˈmaʳkʊs kuːn] <http://www.cl.cam.ac.uk/~mgk25/> — 2002-07-25 CC BY
The ASCII compatible UTF-8 encoding used in this plain-text file
is defined in Unicode, ISO 10646-1, and RFC 2279.
Using Unicode/UTF-8, you can write in emails and source code things such as
Mathematics and sciences:
∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ⎧⎡⎛┌─────┐⎞⎤⎫
⎪⎢⎜│a²+b³ ⎟⎥⎪
∀x∈: ⌈x⌉ = x⌋, α ∧ ¬β = ¬(¬α β), ⎪⎢⎜│───── ⎟⎥⎪
⎪⎢⎜⎷ c₈ ⎟⎥⎪
⊆ ℕ₀ ⊂ , ⎨⎢⎜ ⎟⎥⎬
⎪⎢⎜ ∞ ⎟⎥⎪
⊥ < a ≠ b ≡ c ≤ d ≪ ⇒ (⟦A⟧ ⇔ ⟪B⟫), ⎪⎢⎜ ⎲ ⎟⎥⎪
⎪⎢⎜ ⎳aⁱ-bⁱ⎟⎥⎪
2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm ⎩⎣⎝i=1 ⎠⎦⎭
Linguistics and dictionaries:
ði ıntəˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn
Y [ˈʏpsilɔn], Yen [jɛn], Yoga [ˈjoːgɑ]
APL:
((VV)=V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈
Nicer typography in plain text files:
╔══════════════════════════════════════════╗
║ ║
║ • single and “double” quotes ║
║ ║
║ • Curly apostrophes: “Weve been here” ║
║ ║
║ • Latin-1 apostrophe and accents: '´` ║
║ ║
║ • deutsche „Anführungszeichen“ ║
║ ║
║ • †, ‡, ‰, •, 34, —, 5/+5, ™, … ║
║ ║
║ • ASCII safety test: 1lI|, 0OD, 8B ║
║ ╭─────────╮ ║
║ • the euro symbol: │ 14.95 € │ ║
║ ╰─────────╯ ║
╚══════════════════════════════════════════╝
Combining characters:
STARGΛ̊TE SG-1, a = v̇ = r̈, a⃑ ⊥ b⃑
Greek (in Polytonic):
The Greek anthem:
Σὲ γνωρίζω ἀπὸ τὴν κόψη
τοῦ σπαθιοῦ τὴν τρομερή,
σὲ γνωρίζω ἀπὸ τὴν ὄψη
ποὺ μὲ βία μετράει τὴ γῆ.
᾿Απ᾿ τὰ κόκκαλα βγαλμένη
τῶν ῾Ελλήνων τὰ ἱερά
καὶ σὰν πρῶτα ἀνδρειωμένη
χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά!
From a speech of Demosthenes in the 4th century BC:
Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι,
ὅταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ ὅταν πρὸς τοὺς
λόγους οὓς ἀκούω· τοὺς μὲν γὰρ λόγους περὶ τοῦ
τιμωρήσασθαι Φίλιππον ὁρῶ γιγνομένους, τὰ δὲ πράγματ᾿
εἰς τοῦτο προήκοντα, ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ
πρότερον κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο μοι δοκοῦσιν
οἱ τὰ τοιαῦτα λέγοντες ἢ τὴν ὑπόθεσιν, περὶ ἧς βουλεύεσθαι,
οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁμαρτάνειν. ἐγὼ δέ, ὅτι μέν
ποτ᾿ ἐξῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον
τιμωρήσασθαι, καὶ μάλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ, οὐ πάλαι
γέγονεν ταῦτ᾿ ἀμφότερα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν
προλαβεῖν ἡμῖν εἶναι τὴν πρώτην, ὅπως τοὺς συμμάχους
σώσομεν. ἐὰν γὰρ τοῦτο βεβαίως ὑπάρξῃ, τότε καὶ περὶ τοῦ
τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐξέσται σκοπεῖν· πρὶν δὲ
τὴν ἀρχὴν ὀρθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι περὶ τῆς
τελευτῆς ὁντινοῦν ποιεῖσθαι λόγον.
Δημοσθένους, Γ´ ᾿Ολυνθιακὸς
Georgian:
From a Unicode conference invitation:
გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო
კონფერენციაზე დასასწრებად, რომელიც გაიმართება 10-12 მარტს,
ქ. მაინცში, გერმანიაში. კონფერენცია შეჰკრებს ერთად მსოფლიოს
ექსპერტებს ისეთ დარგებში როგორიცაა ინტერნეტი და Unicode-ი,
ინტერნაციონალიზაცია და ლოკალიზაცია, Unicode-ის გამოყენება
ოპერაციულ სისტემებსა, და გამოყენებით პროგრამებში, შრიფტებში,
ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში.
Russian:
From a Unicode conference invitation:
Зарегистрируйтесь сейчас на Десятую Международную Конференцию по
Unicode, которая состоится 10-12 марта 1997 года в Майнце в Германии.
Конференция соберет широкий круг экспертов по вопросам глобального
Интернета и Unicode, локализации и интернационализации, воплощению и
применению Unicode в различных операционных системах и программных
приложениях, шрифтах, верстке и многоязычных компьютерных системах.
Thai (UCS Level 2):
Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese
classic 'San Gua'):
[----------------------------|------------------------]
๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่
สิบสองกษัตริย์ก่อนหน้าแลถัดไป สององค์ไซร้โง่เขลาเบาปัญญา
ทรงนับถือขันทีเป็นที่พึ่ง บ้านเมืองจึงวิปริตเป็นนักหนา
โฮจิ๋นเรียกทัพทั่วหัวเมืองมา หมายจะฆ่ามดชั่วตัวสำคัญ
เหมือนขับไสไล่เสือจากเคหา รับหมาป่าเข้ามาเลยอาสัญ
ฝ่ายอ้องอุ้นยุแยกให้แตกกัน ใช้สาวนั้นเป็นชนวนชื่นชวนใจ
พลันลิฉุยกุยกีกลับก่อเหตุ ช่างอาเพศจริงหนาฟ้าร้องไห้
ต้องรบราฆ่าฟันจนบรรลัย ฤๅหาใครค้ำชูกู้บรรลังก์ ฯ
(The above is a two-column text. If combining characters are handled
correctly, the lines of the second column should be aligned with the
| character above.)
Ethiopian:
Proverbs in the Amharic language:
ሰማይ አይታረስ ንጉሥ አይከሰስ።
ብላ ካለኝ እንደአባቴ በቆመጠኝ።
ጌጥ ያለቤቱ ቁምጥና ነው።
ደሀ በሕልሙ ቅቤ ባይጠጣ ንጣት በገደለው።
የአፍ ወለምታ በቅቤ አይታሽም።
አይጥ በበላ ዳዋ ተመታ።
ሲተረጉሙ ይደረግሙ።
ቀስ በቀስ፥ ዕንቁላል በእግሩ ይሄዳል።
ድር ቢያብር አንበሳ ያስር።
ሰው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም።
እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድርም።
የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት ያጠልቅ።
ሥራ ከመፍታት ልጄን ላፋታት።
ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል።
የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ።
ተንጋሎ ቢተፉ ተመልሶ ባፉ።
ወዳጅህ ማር ቢሆን ጨርስህ አትላሰው።
እግርህን በፍራሽህ ልክ ዘርጋ።
Runes:
ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ
(Old English, which transcribed into Latin reads 'He cwaeth that he
bude thaem lande northweardum with tha Westsae.' and means 'He said
that he lived in the northern land near the Western Sea.')
Braille:
⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌
⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞
⠱⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎
⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞⠁⠅⠻⠂
⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙
⡎⠊⠗⠕⠕⠛⠑⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑
⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲
⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲
⡍⠔⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅⠝⠪⠂ ⠕⠋ ⠍⠹
⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞
⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕
⠗⠑⠛⠜⠙ ⠁ ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹
⠔ ⠹⠑ ⠞⠗⠁⠙⠑⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕⠗⠎
⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎
⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋⠕⠗⠲ ⡹⠳
⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ ⠹⠁⠞
⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲
(The first couple of paragraphs of "A Christmas Carol" by Dickens)
Compact font selection example text:
ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789
abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ
–—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд
∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi<>⑀₂ἠḂӥẄɐː⍎אԱა
Greetings in various languages:
Hello world, Καλημέρα κόσμε, コンニチハ
Box drawing alignment tests: █
╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
╠╡ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
▝▀▘▙▄▟

View File

@@ -0,0 +1,218 @@
https://www.cogsci.ed.ac.uk/~richard/unicode-sample-3-2.html
This page contains characters from each of the Unicode character blocks.
Basic Latin
! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
Latin-1 Supplement
¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ­ ® ¯ ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ
Latin Extended-A
Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş ş Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž ſ
Latin Extended-B
ƀ Ɓ Ƃ ƃ Ƅ ƅ Ɔ Ƈ ƈ Ɖ Ɗ Ƌ ƌ ƍ Ǝ Ə Ɛ Ƒ ƒ Ɠ Ɣ ƕ Ɩ Ɨ Ƙ ƙ ƚ ƛ Ɯ Ɲ ƞ Ɵ Ơ ơ Ƣ ƣ Ƥ ƥ Ʀ Ƨ ƨ Ʃ ƪ ƫ Ƭ ƭ Ʈ Ư ư Ʊ Ʋ Ƴ ƴ Ƶ ƶ Ʒ Ƹ ƹ ƺ ƻ Ƽ ƽ ƾ ƿ ǀ ǁ ǂ ǃ DŽ Dž dž LJ Lj lj NJ Nj nj Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǖ ǖ Ǘ ǘ Ǚ ǚ Ǜ ǜ ǝ Ǟ ǟ Ǡ ǡ Ǣ ǣ Ǥ ǥ Ǧ ǧ Ǩ ǩ Ǫ ǫ Ǭ ǭ Ǯ ǯ ǰ DZ Dz dz Ǵ ǵ Ƕ Ƿ Ǹ ǹ Ǻ ǻ Ǽ ǽ Ǿ ǿ ...
IPA Extensions
ɐ ɑ ɒ ɓ ɔ ɕ ɖ ɗ ɘ ə ɚ ɛ ɜ ɝ ɞ ɟ ɠ ɡ ɢ ɣ ɤ ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ ɭ ɮ ɯ ɰ ɱ ɲ ɳ ɴ ɵ ɶ ɷ ɸ ɹ ɺ ɻ ɼ ɽ ɾ ɿ ʀ ʁ ʂ ʃ ʄ ʅ ʆ ʇ ʈ ʉ ʊ ʋ ʌ ʍ ʎ ʏ ʐ ʑ ʒ ʓ ʔ ʕ ʖ ʗ ʘ ʙ ʚ ʛ ʜ ʝ ʞ ʟ ʠ ʡ ʢ ʣ ʤ ʥ ʦ ʧ ʨ ʩ ʪ ʫ ʬ ʭ
Spacing Modifier Letters
ʰ ʱ ʲ ʳ ʴ ʵ ʶ ʷ ʸ ʹ ʺ ʻ ʼ ʽ ʾ ʿ ˀ ˁ ˂ ˃ ˄ ˅ ˆ ˇ ˈ ˉ ˊ ˋ ˌ ˍ ˎ ˏ ː ˑ ˒ ˓ ˔ ˕ ˖ ˗ ˘ ˙ ˚ ˛ ˜ ˝ ˞ ˟ ˠ ˡ ˢ ˣ ˤ ˥ ˦ ˧ ˨ ˩ ˪ ˫ ˬ ˭ ˮ
Combining Diacritical Marks
̀ ́ ̂ ̃ ̄ ̅ ̆ ̇ ̈ ̉ ̊ ̋ ̌ ̍ ̎ ̏ ̐ ̑ ̒ ̓ ̔ ̕ ̖ ̗ ̘ ̙ ̚ ̛ ̜ ̝ ̞ ̟ ̠ ̡ ̢ ̣ ̤ ̥ ̦ ̧ ̨ ̩ ̪ ̫ ̬ ̭ ̮ ̯ ̰ ̱ ̲ ̳ ̴ ̵ ̶ ̷ ̸ ̹ ̺ ̻ ̼ ̽ ̾ ̿ ̀ ́ ͂ ̓ ̈́ ͅ ͆ ͇ ͈ ͉ ͊ ͋ ͌ ͍ ͎ ͏ ͠ ͡ ͢ ͣ ͤ ͥ ͦ ͧ ͨ ͩ ͪ ͫ ͬ ͭ ͮ ͯ
Greek and Coptic
ʹ ͵ ͺ ; ΄ ΅ Ά · Έ Ή Ί Ό Ύ Ώ ΐ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω Ϊ Ϋ ά έ ή ί ΰ α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ ς σ τ υ φ χ ψ ω ϊ ϋ ό ύ ώ ϐ ϑ ϒ ϓ ϔ ϕ ϖ ϗ Ϙ ϙ Ϛ ϛ Ϝ ϝ Ϟ ϟ Ϡ ϡ Ϣ ϣ Ϥ ϥ Ϧ ϧ Ϩ ϩ Ϫ ϫ Ϭ ϭ Ϯ ϯ ϰ ϱ ϲ ϳ ϴ ϵ ϶
Cyrillic
Ѐ Ё Ђ Ѓ Є Ѕ І Ї Ј Љ Њ Ћ Ќ Ѝ Ў Џ А Б В Г Д Е Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я ѐ ё ђ ѓ є ѕ і ї ј љ њ ћ ќ ѝ ў џ Ѡ ѡ Ѣ ѣ Ѥ ѥ Ѧ ѧ Ѩ ѩ Ѫ ѫ Ѭ ѭ Ѯ ѯ Ѱ ѱ Ѳ ѳ Ѵ ѵ Ѷ ѷ Ѹ ѹ Ѻ ѻ Ѽ ѽ Ѿ ѿ ...
Cyrillic Supplementary
Ԁ ԁ Ԃ ԃ Ԅ ԅ Ԇ ԇ Ԉ ԉ Ԋ ԋ Ԍ ԍ Ԏ ԏ
Armenian
Ա Բ Գ Դ Ե Զ Է Ը Թ Ժ Ի Լ Խ Ծ Կ Հ Ձ Ղ Ճ Մ Յ Ն Շ Ո Չ Պ Ջ Ռ Ս Վ Տ Ր Ց Ւ Փ Ք Օ Ֆ ՙ ՚ ՛ ՜ ՝ ՞ ՟ ա բ գ դ ե զ է ը թ ժ ի լ խ ծ կ հ ձ ղ ճ մ յ ն շ ո չ պ ջ ռ ս վ տ ր ց ւ փ ք օ ֆ և ։ ֊
Hebrew
֑ ֒ ֓ ֔ ֕ ֖ ֗ ֘ ֙ ֚ ֛ ֜ ֝ ֞ ֟ ֠ ֡ ֣ ֤ ֥ ֦ ֧ ֨ ֩ ֪ ֫ ֬ ֭ ֮ ֯ ְ ֱ ֲ ֳ ִ ֵ ֶ ַ ָ ֹ ֻ ּ ֽ ־ ֿ ׀ ׁ ׂ ׃ ׄ א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ ײ ׳ ״
Arabic
، ؛ ؟ ء آ أ ؤ إ ئ ا ب ة ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ـ ف ق ك ل م ن ه و ى ي ً ٌ ٍ َ ُ ِ ّ ْ ٓ ٔ ٕ ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ ٪ ٫ ٬ ٭ ٮ ٯ ٰ ٱ ٲ ٳ ٴ ٵ ٶ ٷ ٸ ٹ ٺ ٻ ټ ٽ پ ٿ ڀ ځ ڂ ڃ ڄ څ چ ڇ ڈ ډ ڊ ڋ ڌ ڍ ڎ ڏ ڐ ڑ ڒ ړ ڔ ڕ ږ ڗ ژ ڙ ښ ڛ ڜ ڝ ڞ ڟ ڠ ڡ ڢ ڣ ڤ ڥ ڦ ڧ ڨ ک ڪ ګ ڬ ...
Syriac
܀ ܁ ܂ ܃ ܄ ܅ ܆ ܇ ܈ ܉ ܊ ܋ ܌ ܍ ܏ ܐ ܑ ܒ ܓ ܔ ܕ ܖ ܗ ܘ ܙ ܚ ܛ ܜ ܝ ܞ ܟ ܠ ܡ ܢ ܣ ܤ ܥ ܦ ܧ ܨ ܩ ܪ ܫ ܬ ܰ ܱ ܲ ܳ ܴ ܵ ܶ ܷ ܸ ܹ ܺ ܻ ܼ ܽ ܾ ܿ ݀ ݁ ݂ ݃ ݄ ݅ ݆ ݇ ݈ ݉ ݊
Thaana
ހ ށ ނ ރ ބ ޅ ކ އ ވ މ ފ ދ ތ ލ ގ ޏ ސ ޑ ޒ ޓ ޔ ޕ ޖ ޗ ޘ ޙ ޚ ޛ ޜ ޝ ޞ ޟ ޠ ޡ ޢ ޣ ޤ ޥ ަ ާ ި ީ ު ޫ ެ ޭ ޮ ޯ ް ޱ
Devanagari
ँ ं अ आ इ ई उ ऊ ऋ ऌ ऍ ऎ ए ऐ ऑ ऒ ओ औ क ख ग घ ङ च छ ज झ ञ ट ठ ड ढ ण त थ द ध न ऩ प फ ब भ म य र ऱ ल ळ ऴ व श ष स ह ़ ऽ ा ि ी ु ू ृ ॄ ॅ ॆ े ै ॉ ॊ ो ौ ् ॐ ॑ ॒ ॓ ॔ क़ ख़ ग़ ज़ ड़ ढ़ फ़ य़ ॠ ॡ ॢ ॣ । ॥ १ २ ३ ४ ५ ६ ७ ८ ९ ॰
Bengali
ঁ ং ঃ অ আ ই ঈ উ ঊ ঋ ঌ এ ঐ ও ঔ ক খ গ ঘ ঙ চ ছ জ ঝ ঞ ট ঠ ড ঢ ণ ত থ দ ধ ন প ফ ব ভ ম য র ল শ ষ স হ ় া ি ী ু ূ ৃ ৄ ে ৈ ো ৌ ্ ৗ ড় ঢ় য় ৠ ৡ ৢ ৣ ১ ২ ৩ ৫ ৬ ৮ ৯ ৰ ৱ ৲ ৳ ৴ ৵ ৶ ৷ ৸ ৹ ৺
Gurmukhi
ਂ ਅ ਆ ਇ ਈ ਉ ਊ ਏ ਐ ਓ ਔ ਕ ਖ ਗ ਘ ਙ ਚ ਛ ਜ ਝ ਞ ਟ ਠ ਡ ਢ ਣ ਤ ਥ ਦ ਧ ਨ ਪ ਫ ਬ ਭ ਮ ਯ ਰ ਲ ਲ਼ ਵ ਸ਼ ਸ ਹ ਼ ਾ ਿ ੀ ੁ ੂ ੇ ੈ ੋ ੌ ੍ ਖ਼ ਗ਼ ਜ਼ ੜ ਫ਼ ੨ ੩ ੫ ੬ ੭ ੮ ੯ ੰ ੱ ੲ ੳ ੴ
Gujarati
ઁ ં અ આ ઇ ઈ ઉ ઊ ઋ ઍ એ ઐ ઑ ઓ ઔ ક ખ ગ ઘ ઙ ચ છ જ ઝ ઞ ટ ઠ ડ ઢ ણ ત થ દ ધ ન પ ફ બ ભ મ ય ર લ ળ વ શ ષ સ હ ઼ ઽ ા િ ી ુ ૂ ૃ ૄ ૅ ે ૈ ૉ ો ૌ ્ ૐ ૠ ૧ ૨ ૩ ૪ ૫ ૬ ૭ ૮ ૯
Oriya
ଁ ଂ ଅ ଆ ଇ ଈ ଉ ଊ ଋ ଌ ଏ ଐ ଓ ଔ କ ଖ ଗ ଘ ଙ ଚ ଛ ଜ ଝ ଞ ଟ ଡ ଢ ଣ ତ ଥ ଦ ଧ ନ ପ ଫ ବ ଭ ମ ଯ ର ଲ ଳ ଶ ଷ ସ ହ ଼ ଽ ା ି ୀ ୁ ୂ ୃ େ ୈ ୋ ୌ ୍ ୖ ୗ ଡ଼ ଢ଼ ୟ ୠ ୡ ୩ ୪ ୫ ୬ ୭ ୮ ୯ ୰
Tamil
ஂ ஃ அ ஆ இ ஈ உ ஊ எ ஏ ஐ ஒ ஓ ஔ க ங ச ஜ ஞ ட ண த ந ன ப ம ய ர ற ல ள ழ வ ஷ ஸ ஹ ா ி ீ ு ூ ெ ே ை ொ ோ ௌ ் ௗ ௧ ௨ ௩ ௪ ௫ ௬ ௭ ௮ ௯ ௰ ௱ ௲
Telugu
ః అ ఆ ఇ ఈ ఉ ఊ ఋ ఌ ఎ ఏ ఐ ఒ ఓ ఔ క ఖ గ ఘ ఙ చ ఛ జ ఝ ఞ ట ఠ డ ఢ ణ త థ ద ధ న ప ఫ బ భ మ య ర ఱ ల ళ వ శ ష స హ ా ి ీ ు ూ ృ ౄ ె ే ై ొ ో ౌ ్ ౕ ౖ ౠ ౡ ౧ ౨ ౩ ౪ ౫ ౬ ౭ ౮ ౯
Kannada
ಃ ಅ ಆ ಇ ಈ ಉ ಊ ಋ ಌ ಎ ಏ ಐ ಒ ಓ ಔ ಕ ಖ ಗ ಘ ಙ ಚ ಛ ಜ ಝ ಞ ಟ ಠ ಡ ಢ ಣ ತ ಥ ದ ಧ ನ ಪ ಫ ಬ ಭ ಮ ಯ ರ ಱ ಲ ಳ ವ ಶ ಷ ಸ ಹ ಾ ಿ ೀ ು ೂ ೃ ೄ ೆ ೇ ೈ ೊ ೋ ೌ ್ ೕ ೖ ೞ ೠ ೡ ೧ ೨ ೩ ೪ ೫ ೬ ೭ ೮ ೯
Malayalam
ഃ അ ആ ഇ ഈ ഉ ഊ ഋ ഌ എ ഏ ഐ ഒ ഓ ഔ ക ഖ ഗ ഘ ങ ച ഛ ജ ഝ ഞ ട ഡ ഢ ണ ത ഥ ദ ധ ന പ ഫ ബ ഭ മ യ ര റ ല ള ഴ വ ശ ഷ സ ഹ ാ ി ീ ു ൂ ൃ െ േ ൈ ൊ ോ ൌ ് ൗ ൠ ൡ ൧ ൨ ൩ ൪ ൫ ൬ ൮ ൯
Sinhala
ඃ අ ආ ඇ ඈ ඉ ඊ උ ඌ ඍ ඎ ඏ ඐ එ ඒ ඓ ඔ ඕ ඖ ක ඛ ග ඝ ඞ ඟ ච ඡ ජ ඣ ඤ ඥ ඦ ට ඨ ඩ ඪ ණ ඬ ත ථ ද ධ න ඳ ප ඵ බ භ ම ඹ ය ර ල ව ශ ෂ ස හ ළ ෆ ් ා ැ ෑ ි ී ු ූ ෘ ෙ ේ ෛ ො ෝ ෞ ෟ ෲ ෳ ෴
Thai
ก ข ฃ ค ฅ ฆ ง จ ฉ ช ซ ฌ ญ ฎ ฏ ฐ ฑ ฒ ณ ด ต ถ ท ธ น บ ป ผ ฝ พ ฟ ภ ม ย ร ฤ ล ฦ ว ศ ษ ส ห ฬ อ ฮ ฯ ะ ั า ำ ิ ี ึ ื ุ ู ฺ ฿ เ แ โ ใ ไ ๅ ๆ ็ ่ ้ ๊ ๋ ์ ํ ๎ ๏ ๑ ๒ ๓ ๔ ๕ ๖ ๗ ๘ ๙ ๚ ๛
Lao
ກ ຂ ຄ ງ ຈ ຊ ຍ ດ ຕ ຖ ທ ນ ບ ປ ຜ ຝ ພ ຟ ມ ຢ ຣ ລ ວ ສ ຫ ອ ຮ ຯ ະ ັ າ ຳ ິ ີ ຶ ື ຸ ູ ົ ຼ ຽ ເ ແ ໂ ໃ ໄ ໆ ່ ້ ໊ ໋ ໌ ໍ ໑ ໒ ໓ ໔ ໕ ໖ ໗ ໘ ໙ ໜ ໝ
Tibetan
ༀ ༁ ༂ ༃ ༄ ༅ ༆ ༇ ༈ ༉ ༊ ་ ༌ ། ༎ ༏ ༐ ༑ ༒ ༓ ༔ ༕ ༖ ༗ ༘ ༙ ༚ ༛ ༜ ༝ ༞ ༟ ༠ ༡ ༢ ༣ ༤ ༥ ༦ ༧ ༨ ༩ ༪ ༫ ༬ ༭ ༮ ༯ ༰ ༱ ༲ ༳ ༴ ༵ ༶ ༷ ༸ ༹ ༺ ༻ ༼ ༽ ༾ ༿ ཀ ཁ ག གྷ ང ཅ ཆ ཇ ཉ ཊ ཋ ཌ ཌྷ ཎ ཏ ཐ ད དྷ ན པ ཕ བ བྷ མ ཙ ཚ ཛ ཛྷ ཝ ཞ ཟ འ ཡ ར ལ ཤ ཥ ས ཧ ཨ ཀྵ ཪ ཱ ི ཱི ུ ཱུ ྲྀ ཷ ླྀ ཹ ེ ཻ ོ ཽ ཾ ཿ ྀ ཱྀ ྂ ྃ ྄ ྅ ྆ ...
Myanmar
က ခ ဂ ဃ င စ ဆ ဇ ဈ ဉ ည ဋ ဌ ဍ ဎ ဏ တ ထ ဒ ဓ န ပ ဖ ဗ ဘ မ ယ ရ လ သ ဟ ဠ အ ဣ ဤ ဥ ဦ ဧ ဩ ဪ ာ ိ ီ ု ူ ေ ဲ ံ ့ း ္ ၁ ၂ ၃ ၄ ၅ ၆ ၇ ၈ ၉ ၊ ။ ၌ ၍ ၎ ၏ ၐ ၑ ၒ ၓ ၔ ၕ ၖ ၗ ၘ ၙ
Georgian
Ⴀ Ⴁ Ⴂ Ⴃ Ⴄ Ⴅ Ⴆ Ⴇ Ⴈ Ⴉ Ⴊ Ⴋ Ⴌ Ⴍ Ⴎ Ⴏ Ⴐ Ⴑ Ⴒ Ⴓ Ⴔ Ⴕ Ⴖ Ⴗ Ⴘ Ⴙ Ⴚ Ⴛ Ⴜ Ⴝ Ⴞ Ⴟ Ⴠ Ⴡ Ⴢ Ⴣ Ⴤ Ⴥ ა ბ გ დ ე ვ ზ თ ი კ ლ მ ნ ო პ ჟ რ ს ტ უ ფ ქ ღ შ ჩ ც ძ წ ჭ ხ ჯ ჰ ჱ ჲ ჳ ჴ ჵ ჶ ჷ ჸ ჻
Hangul Jamo
ᄀ ᄁ ᄂ ᄃ ᄄ ᄅ ᄆ ᄇ ᄈ ᄉ ᄊ ᄋ ᄌ ᄍ ᄎ ᄏ ᄐ ᄑ ᄒ ᄓ ᄔ ᄕ ᄖ ᄗ ᄘ ᄙ ᄚ ᄛ ᄜ ᄝ ᄞ ᄟ ᄠ ᄡ ᄢ ᄣ ᄤ ᄥ ᄦ ᄧ ᄨ ᄩ ᄪ ᄫ ᄬ ᄭ ᄮ ᄯ ᄰ ᄱ ᄲ ᄳ ᄴ ᄵ ᄶ ᄷ ᄸ ᄹ ᄺ ᄻ ᄼ ᄽ ᄾ ᄿ ᅀ ᅁ ᅂ ᅃ ᅄ ᅅ ᅆ ᅇ ᅈ ᅉ ᅊ ᅋ ᅌ ᅍ ᅎ ᅏ ᅐ ᅑ ᅒ ᅓ ᅔ ᅕ ᅖ ᅗ ᅘ ᅙ ᅡ ᅢ ᅣ ᅤ ᅥ ᅦ ᅧ ᅨ ᅩ ᅪ ᅫ ᅬ ᅭ ᅮ ᅯ ᅰ ᅱ ᅲ ᅳ ᅴ ᅵ ᅶ ᅷ ᅸ ᅹ ᅺ ᅻ ᅼ ᅽ ᅾ ᅿ ᆀ ᆁ ᆂ ᆃ ᆄ ...
Ethiopic
ሁ ሂ ሃ ሄ ህ ሆ ለ ሉ ሊ ላ ሌ ል ሎ ሏ ሐ ሑ ሒ ሓ ሔ ሕ ሖ ሗ መ ሙ ሚ ማ ሜ ም ሞ ሟ ሠ ሡ ሢ ሣ ሤ ሥ ሦ ሧ ረ ሩ ሪ ራ ሬ ር ሮ ሯ ሰ ሱ ሲ ሳ ሴ ስ ሶ ሷ ሸ ሹ ሺ ሻ ሼ ሽ ሾ ሿ ቀ ቁ ቂ ቃ ቄ ቅ ቆ ቈ ቊ ቋ ቌ ቍ ቐ ቑ ቒ ቓ ቔ ቕ ቖ ቘ ቚ ቛ ቜ ቝ በ ቡ ቢ ባ ቤ ብ ቦ ቧ ቨ ቩ ቪ ቫ ቬ ቭ ቮ ቯ ተ ቱ ቲ ታ ቴ ት ቶ ቷ ቸ ቹ ቺ ቻ ቼ ች ቾ ቿ ኀ ኁ ኂ ኃ ኄ ኅ ኆ ኈ ኊ ...
Cherokee
Ꭳ Ꭴ Ꭶ Ꭷ Ꭸ Ꭿ Ꮀ Ꮁ Ꮂ Ꮄ Ꮅ Ꮆ Ꮈ Ꮉ Ꮊ Ꮎ Ꮏ Ꮔ Ꮕ Ꮖ Ꮗ Ꮘ Ꮙ Ꮚ Ꮛ Ꮜ Ꮝ Ꮠ Ꮡ Ꮦ Ꮧ Ꮨ Ꮫ Ꮬ Ꮭ Ꮰ Ꮱ Ꮳ Ꮴ Ꮵ Ꮸ Ꮹ Ꮺ Ꮻ Ꮼ Ꮽ Ꮿ Ᏸ Ᏹ Ᏺ
Unified Canadian Aboriginal Syllabics
ᐁ ᐂ ᐃ ᐄ ᐅ ᐆ ᐇ ᐈ ᐉ ᐊ ᐋ ᐌ ᐍ ᐎ ᐏ ᐐ ᐑ ᐒ ᐓ ᐔ ᐕ ᐖ ᐗ ᐘ ᐙ ᐚ ᐛ ᐜ ᐝ ᐞ ᐟ ᐠ ᐡ ᐢ ᐣ ᐤ ᐥ ᐦ ᐧ ᐨ ᐩ ᐪ ᐫ ᐬ ᐭ ᐮ ᐰ ᐱ ᐲ ᐴ ᐵ ᐶ ᐷ ᐹ ᐺ ᐻ ᐼ ᐽ ᐾ ᐿ ᑀ ᑁ ᑂ ᑃ ᑄ ᑅ ᑆ ᑇ ᑈ ᑉ ᑍ ᑎ ᑏ ᑐ ᑑ ᑒ ᑓ ᑔ ᑕ ᑖ ᑗ ᑘ ᑙ ᑚ ᑛ ᑜ ᑝ ᑞ ᑟ ᑠ ᑡ ᑢ ᑣ ᑤ ᑥ ᑦ ᑧ ᑨ ᑩ ᑪ ᑫ ᑬ ᑰ ᑱ ᑳ ᑴ ᑵ ᑶ ᑷ ᑸ ᑹ ᑺ ᑻ ᑼ ᑽ ᑾ ᑿ ᒀ ...
Ogham
ᚁ ᚂ ᚃ ᚄ ᚅ ᚆ ᚇ ᚈ ᚉ ᚊ ᚋ ᚌ ᚍ ᚎ ᚏ ᚐ ᚑ ᚒ ᚓ ᚔ ᚕ ᚖ ᚗ ᚘ ᚙ ᚚ ᚛ ᚜
Runic
ᚠ ᚡ ᚢ ᚣ ᚤ ᚥ ᚦ ᚧ ᚨ ᚩ ᚪ ᚫ ᚬ ᚭ ᚮ ᚯ ᚰ ᚱ ᚳ ᚴ ᚵ ᚶ ᚸ ᚹ ᚺ ᚻ ᚼ ᚽ ᚾ ᚿ ᛀ ᛂ ᛃ ᛄ ᛅ ᛆ ᛇ ᛈ ᛉ ᛊ ᛋ ᛍ ᛎ ᛏ ᛐ ᛑ ᛒ ᛓ ᛔ ᛗ ᛘ ᛙ ᛚ ᛛ ᛜ ᛝ ᛞ ᛟ ᛠ ᛡ ᛢ ᛣ ᛤ ᛥ ᛦ ᛧ ᛨ ᛩ ᛪ ᛫ ᛮ ᛯ ᛰ
Tagalog
ᜀ ᜁ ᜂ ᜃ ᜄ ᜅ ᜆ ᜇ ᜈ ᜉ ᜊ ᜋ ᜌ ᜎ ᜏ ᜐ ᜑ ᜒ ᜓ ᜔
Hanunoo
ᜠ ᜡ ᜢ ᜣ ᜤ ᜥ ᜦ ᜧ ᜨ ᜩ ᜪ ᜫ ᜬ ᜭ ᜮ ᜯ ᜰ ᜱ ᜲ ᜳ ᜴
Buhid
ᝀ ᝁ ᝂ ᝃ ᝄ ᝅ ᝆ ᝇ ᝈ ᝉ ᝊ ᝋ ᝌ ᝍ ᝎ ᝏ ᝐ ᝑ ᝒ ᝓ
Tagbanwa
ᝠ ᝡ ᝢ ᝣ ᝤ ᝥ ᝦ ᝧ ᝨ ᝩ ᝪ ᝫ ᝬ ᝮ ᝯ ᝰ ᝲ ᝳ
Khmer
ក ខ គ ឃ ង ច ឆ ជ ឈ ញ ដ ឋ ឌ ឍ ណ ត ថ ទ ធ ន ប ផ ព ភ ម យ រ ល វ ឝ ឞ ស ហ ឡ អ ឣ ឤ ឥ ឦ ឧ ឨ ឩ ឪ ឫ ឬ ឭ ឮ ឯ ឰ ឱ ឲ ឳ ា ិ ី ឹ ឺ ុ ូ ួ ើ ឿ ៀ េ ែ ៃ ោ ៅ ំ ះ ៈ ៉ ៊ ់ ៌ ៍ ៎ ៏ ័ ៑ ្ ៓ ។ ៕ ៖ ៗ ៘ ៙ ៚ ៛ ៜ ០ ១ ២ ៣ ៤ ៥ ៦ ៧ ៨ ៩
Mongolian
᠀ ᠁ ᠂ ᠄ ᠅ ᠆ ᠇ ᠈ ᠐ ᠑ ᠒ ᠓ ᠔ ᠕ ᠖ ᠗ ᠘ ᠙ ᠠ ᠡ ᠢ ᠣ ᠤ ᠥ ᠦ ᠧ ᠨ ᠩ ᠪ ᠫ ᠬ ᠭ ᠮ ᠯ ᠰ ᠱ ᠲ ᠳ ᠴ ᠵ ᠶ ᠷ ᠸ ᠹ ᠺ ᠻ ᠼ ᠽ ᠾ ᠿ ᡀ ᡁ ᡂ ᡃ ᡄ ᡅ ᡆ ᡇ ᡈ ᡉ ᡊ ᡋ ᡌ ᡍ ᡎ ᡏ ᡐ ᡑ ᡒ ᡓ ᡔ ᡕ ᡖ ᡗ ᡘ ᡙ ᡚ ᡛ ᡜ ᡝ ᡞ ᡟ ᡠ ᡡ ᡢ ᡣ ᡤ ᡥ ᡦ ᡧ ᡨ ᡩ ᡪ ᡫ ᡬ ᡭ ᡮ ᡯ ᡰ ᡱ ᡲ ᡳ ᡴ ᡵ ᡶ ᡷ ᢀ ᢁ ᢂ ᢃ ᢄ ᢅ ᢆ ᢇ ᢈ ᢉ ᢊ ᢋ ᢌ ᢍ ᢎ ...
Latin Extended Additional
Ḁ ḁ Ḃ ḃ Ḅ ḅ Ḇ ḇ Ḉ ḉ Ḋ ḋ Ḍ ḍ Ḏ ḏ Ḑ ḑ Ḓ ḓ Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḝ Ḟ ḟ Ḡ ḡ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ Ḭ ḭ Ḯ ḯ Ḱ ḱ Ḳ ḳ Ḵ ḵ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ Ḿ ḿ Ṁ ṁ Ṃ ṃ Ṅ ṅ Ṇ ṇ Ṉ ṉ Ṋ ṋ Ṍ ṍ Ṏ ṏ Ṑ ṑ Ṓ ṓ Ṕ ṕ Ṗ ṗ Ṙ ṙ Ṛ ṛ Ṝ ṝ Ṟ ṟ Ṡ ṡ Ṣ ṣ Ṥ ṥ Ṧ ṧ Ṩ ṩ Ṫ ṫ Ṭ ṭ Ṯ ṯ Ṱ ṱ Ṳ ṳ Ṵ ṵ Ṷ ṷ Ṹ ṹ Ṻ ṻ Ṽ ṽ Ṿ ṿ ...
Greek Extended
ἀ ἁ ἂ ἃ ἄ ἅ ἆ ἇ Ἀ Ἁ Ἂ Ἃ Ἄ Ἅ Ἆ Ἇ ἐ ἑ ἒ ἓ ἔ ἕ Ἐ Ἑ Ἒ Ἓ Ἔ Ἕ ἠ ἡ ἢ ἣ ἤ ἥ ἦ ἧ Ἠ Ἡ Ἢ Ἣ Ἤ Ἥ Ἦ Ἧ ἰ ἱ ἲ ἳ ἴ ἵ ἶ ἷ Ἰ Ἱ Ἲ Ἳ Ἴ Ἵ Ἶ Ἷ ὀ ὁ ὂ ὃ ὄ ὅ Ὀ Ὁ Ὂ Ὃ Ὄ Ὅ ὐ ὑ ὒ ὓ ὔ ὕ ὖ ὗ Ὑ Ὓ Ὕ Ὗ ὠ ὡ ὢ ὣ ὤ ὥ ὦ ὧ Ὠ Ὡ Ὢ Ὣ Ὤ Ὥ Ὦ Ὧ ὰ ά ὲ έ ὴ ή ὶ ί ὸ ό ὺ ύ ὼ ώ ᾀ ᾁ ᾂ ᾃ ᾄ ᾅ ᾆ ᾇ ᾈ ᾉ ᾊ ᾋ ᾌ ᾍ ...
General Punctuation
  — ― ‖ ‗ “ ” „ ‟ † ‡ • ‣ ‥ … ‧
 ‰ ‱ ″ ‴ ‶ ‷ ‸ ※ ‼ ‽ ‾ ‿ ⁀ ⁅ ⁆ ⁇ ⁈ ⁉ ⁊ ⁋ ⁌ ⁍ ⁏ ⁐ ⁑ ⁒ ⁗
Superscripts and Subscripts
⁰ ⁱ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ⁿ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₌ ₍ ₎
Currency Symbols
₠ ₡ ₢ ₣ ₤ ₥ ₦ ₧ ₨ ₩ ₪ ₫ € ₭ ₮ ₯ ₰ ₱
Combining Diacritical Marks for Symbols
⃐ ⃑ ⃒ ⃓ ⃔ ⃕ ⃖ ⃗ ⃘ ⃙ ⃚ ⃛ ⃜ ⃝ ⃞ ⃟ ⃠ ⃡ ⃢ ⃣ ⃤ ⃥ ⃦ ⃧ ⃨ ⃩ ⃪
Letterlike Symbols
℀ ℁ ℃ ℄ ℅ ℆ ℇ ℈ ℉ № ℗ ℘ ℞ ℟ ℠ ℡ ™ ℣ ℥ Ω ℧ ℵ ℶ ℷ ℸ ℾ ℿ ⅀ ⅁ ⅂ ⅃ ⅄ ⅊ ⅋
Number Forms
⅓ ⅔ ⅕ ⅖ ⅗ ⅘ ⅙ ⅚ ⅛ ⅜ ⅝ ⅞ ⅟ Ⅱ Ⅲ Ⅳ Ⅵ Ⅶ Ⅷ Ⅸ Ⅺ Ⅻ ⅱ ⅲ ⅳ ⅵ ⅶ ⅷ ⅸ ⅺ ⅻ ⅿ ↀ ↁ ↂ Ↄ
Arrows
← ↑ → ↓ ↔ ↕ ↖ ↗ ↘ ↙ ↚ ↛ ↜ ↝ ↞ ↟ ↠ ↡ ↢ ↣ ↤ ↥ ↦ ↧ ↨ ↩ ↪ ↫ ↬ ↭ ↮ ↯ ↰ ↱ ↲ ↳ ↴ ↵ ↶ ↷ ↸ ↹ ↺ ↻ ↼ ↽ ↾ ↿ ⇀ ⇁ ⇂ ⇃ ⇄ ⇅ ⇆ ⇇ ⇈ ⇉ ⇊ ⇋ ⇌ ⇍ ⇎ ⇏ ⇐ ⇑ ⇒ ⇓ ⇔ ⇕ ⇖ ⇗ ⇘ ⇙ ⇚ ⇛ ⇜ ⇝ ⇞ ⇟ ⇠ ⇡ ⇢ ⇣ ⇤ ⇥ ⇦ ⇧ ⇨ ⇩ ⇪ ⇫ ⇬ ⇭ ⇮ ⇯ ⇰ ⇱ ⇲ ⇳ ⇴ ⇵ ⇶ ⇷ ⇸ ⇹ ⇺ ⇻ ⇼ ⇽ ⇾ ⇿
Mathematical Operators
∀ ∁ ∂ ∃ ∄ ∅ ∆ ∇ ∈ ∉ ∊ ∋ ∌ ∍ ∎ ∏ ∐ ∑ ∓ ∔ ∘ ∙ √ ∛ ∜ ∝ ∞ ∟ ∠ ∡ ∢ ∤ ∥ ∦ ∧ ∫ ∬ ∭ ∮ ∯ ∰ ∱ ∲ ∳ ∴ ∵ ∷ ∸ ∹ ∺ ∻ ∽ ∾ ∿ ≀ ≁ ≂ ≃ ≄ ≅ ≆ ≇ ≈ ≉ ≊ ≋ ≌ ≍ ≎ ≏ ≐ ≑ ≒ ≓ ≔ ≕ ≖ ≗ ≘ ≙ ≚ ≛ ≜ ≝ ≞ ≟ ≠ ≡ ≢ ≣ ≤ ≥ ≦ ≧ ≨ ≩ ≪ ≫ ≬ ≭ ≮ ≯ ≰ ≱ ≲ ≳ ≴ ≵ ≶ ≷ ≸ ≹ ≺ ≻ ≼ ≽ ≾ ≿ ...
Miscellaneous Technical
⌀ ⌁ ⌂ ⌃ ⌄ ⌅ ⌆ ⌇ ⌈ ⌉ ⌊ ⌋ ⌌ ⌍ ⌎ ⌏ ⌐ ⌑ ⌒ ⌓ ⌔ ⌕ ⌖ ⌗ ⌘ ⌙ ⌚ ⌛ ⌜ ⌝ ⌞ ⌟ ⌠ ⌡ ⌢ ⌣ ⌤ ⌥ ⌦ ⌧ ⌨ 〈 〉 ⌫ ⌬ ⌭ ⌮ ⌯ ⌰ ⌱ ⌲ ⌳ ⌴ ⌵ ⌶ ⌷ ⌸ ⌹ ⌺ ⌻ ⌼ ⌽ ⌾ ⌿ ⍀ ⍁ ⍂ ⍃ ⍄ ⍅ ⍆ ⍇ ⍈ ⍉ ⍊ ⍋ ⍌ ⍍ ⍎ ⍏ ⍐ ⍑ ⍒ ⍓ ⍔ ⍕ ⍖ ⍗ ⍘ ⍙ ⍚ ⍛ ⍜ ⍝ ⍞ ⍟ ⍠ ⍡ ⍢ ⍣ ⍤ ⍥ ⍦ ⍧ ⍨ ⍩ ⍪ ⍫ ⍬ ⍭ ⍮ ⍯ ⍰ ⍱ ⍲ ⍵ ⍶ ⍷ ⍸ ⍹ ⍻ ⍼ ⍽ ⍾ ⍿ ...
Control Pictures
␀ ␁ ␂ ␃ ␄ ␅ ␆ ␇ ␈ ␉ ␊ ␋ ␌ ␍ ␎ ␏ ␐ ␑ ␒ ␓ ␔ ␕ ␖ ␗ ␘ ␙ ␚ ␛ ␜ ␝ ␞ ␟ ␠ ␡ ␢ ␣ ␤ ␥ ␦
Optical Character Recognition
⑀ ⑁ ⑂ ⑃ ⑄ ⑅ ⑆ ⑇ ⑈ ⑉ ⑊
Enclosed Alphanumerics
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ...
Box Drawing
─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ ╰ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
Block Elements
▀ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▉ ▊ ▋ ▌ ▍ ▎ ▏ ▐ ░ ▒ ▓ ▔ ▕ ▖ ▗ ▘ ▙ ▚ ▛ ▜ ▝ ▞ ▟
Geometric Shapes
■ □ ▢ ▣ ▤ ▥ ▦ ▧ ▨ ▩ ▪ ▫ ▬ ▭ ▮ ▯ ▰ ▱ ▲ △ ▴ ▵ ▶ ▷ ▸ ▹ ► ▻ ▼ ▽ ▾ ▿ ◀ ◁ ◂ ◃ ◄ ◅ ◆ ◇ ◈ ◉ ◊ ○ ◌ ◍ ◎ ● ◐ ◑ ◒ ◓ ◔ ◕ ◖ ◗ ◘ ◙ ◚ ◛ ◜ ◝ ◞ ◟ ◠ ◡ ◢ ◣ ◤ ◥ ◦ ◧ ◨ ◩ ◪ ◫ ◬ ◭ ◮ ◯ ◰ ◱ ◲ ◳ ◴ ◵ ◶ ◷ ◸ ◹ ◺ ◻ ◼ ◽ ◾ ◿
Miscellaneous Symbols
☀ ☁ ☂ ☃ ☄ ★ ☆ ☇ ☈ ☉ ☊ ☋ ☌ ☍ ☎ ☏ ☐ ☑ ☒ ☓ ☖ ☗ ☙ ☚ ☛ ☜ ☝ ☞ ☟ ☠ ☡ ☢ ☣ ☤ ☥ ☦ ☧ ☨ ☩ ☪ ☫ ☬ ☭ ☮ ☯ ☰ ☱ ☲ ☳ ☴ ☵ ☶ ☷ ☸ ☹ ☺ ☻ ☼ ☽ ☾ ☿ ♀ ♁ ♂ ♃ ♄ ♅ ♆ ♇ ♈ ♉ ♊ ♋ ♌ ♍ ♎ ♏ ♐ ♑ ♒ ♓ ♔ ♕ ♖ ♗ ♘ ♙ ♚ ♛ ♜ ♝ ♞ ♟ ♠ ♡ ♢ ♣ ♤ ♥ ♦ ♧ ♨ ♩ ♪ ♫ ♬ ♭ ♮ ♯ ♰ ♱ ♲ ♳ ♴ ♵ ♶ ♷ ♸ ♹ ♺ ♻ ♼ ♽ ⚀ ⚁ ⚂ ⚃ ⚄ ...
Dingbats
✁ ✂ ✃ ✄ ✆ ✇ ✈ ✉ ✌ ✍ ✎ ✏ ✐ ✑ ✒ ✓ ✔ ✕ ✖ ✗ ✘ ✙ ✚ ✛ ✜ ✝ ✞ ✟ ✠ ✡ ✢ ✣ ✤ ✥ ✦ ✧ ✩ ✪ ✫ ✬ ✭ ✮ ✯ ✰ ✱ ✲ ✳ ✴ ✵ ✶ ✷ ✸ ✹ ✺ ✻ ✼ ✽ ✾ ✿ ❀ ❁ ❂ ❃ ❄ ❅ ❆ ❇ ❈ ❉ ❊ ❋ ❍ ❏ ❐ ❑ ❒ ❖ ❘ ❙ ❚ ❛ ❜ ❝ ❞ ❡ ❢ ❣ ❤ ❥ ❦ ❧ ❪ ❫ ❬ ❭ ❰ ❱ ❶ ❷ ❸ ❹ ❺ ❻ ❼ ❽ ❾ ❿ ➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉ ➊ ➋ ➌ ...
Miscellaneous Mathematical Symbols-A
⟐ ⟑ ⟒ ⟓ ⟔ ⟕ ⟖ ⟗ ⟘ ⟚ ⟛ ⟜ ⟝ ⟞ ⟟ ⟠ ⟡ ⟢ ⟣ ⟤ ⟥ ⟦ ⟧ ⟨ ⟩ ⟪ ⟫
Supplemental Arrows-A
⟰ ⟱ ⟲ ⟳ ⟴ ⟵ ⟶ ⟷ ⟸ ⟹ ⟺ ⟻ ⟼ ⟽ ⟾ ⟿
Braille Patterns
⠁ ⠂ ⠃ ⠄ ⠅ ⠆ ⠇ ⠈ ⠉ ⠊ ⠋ ⠌ ⠍ ⠎ ⠏ ⠐ ⠑ ⠒ ⠓ ⠔ ⠕ ⠖ ⠗ ⠘ ⠙ ⠚ ⠛ ⠜ ⠝ ⠞ ⠟ ⠠ ⠡ ⠢ ⠣ ⠤ ⠥ ⠦ ⠧ ⠨ ⠩ ⠪ ⠫ ⠬ ⠭ ⠮ ⠯ ⠰ ⠱ ⠲ ⠳ ⠴ ⠵ ⠶ ⠷ ⠸ ⠹ ⠺ ⠻ ⠼ ⠽ ⠾ ⠿ ⡀ ⡁ ⡂ ⡃ ⡄ ⡅ ⡆ ⡇ ⡈ ⡉ ⡊ ⡋ ⡌ ⡍ ⡎ ⡏ ⡐ ⡑ ⡒ ⡓ ⡔ ⡕ ⡖ ⡗ ⡘ ⡙ ⡚ ⡛ ⡜ ⡝ ⡞ ⡟ ⡠ ⡡ ⡢ ⡣ ⡤ ⡥ ⡦ ⡧ ⡨ ⡩ ⡪ ⡫ ⡬ ⡭ ⡮ ⡯ ⡰ ⡱ ⡲ ⡳ ⡴ ⡵ ⡶ ⡷ ⡸ ⡹ ⡺ ⡻ ⡼ ⡽ ⡾ ⡿ ...
Supplemental Arrows-B
⤀ ⤁ ⤂ ⤃ ⤄ ⤅ ⤆ ⤇ ⤈ ⤉ ⤊ ⤋ ⤌ ⤍ ⤎ ⤏ ⤐ ⤑ ⤒ ⤓ ⤔ ⤕ ⤖ ⤗ ⤘ ⤙ ⤚ ⤛ ⤜ ⤝ ⤞ ⤟ ⤠ ⤡ ⤢ ⤣ ⤤ ⤥ ⤦ ⤧ ⤨ ⤩ ⤪ ⤭ ⤮ ⤯ ⤰ ⤱ ⤲ ⤳ ⤴ ⤵ ⤶ ⤷ ⤸ ⤹ ⤺ ⤻ ⤼ ⤽ ⤾ ⤿ ⥀ ⥁ ⥂ ⥃ ⥄ ⥅ ⥆ ⥇ ⥈ ⥉ ⥊ ⥋ ⥌ ⥍ ⥎ ⥏ ⥐ ⥑ ⥒ ⥓ ⥔ ⥕ ⥖ ⥗ ⥘ ⥙ ⥚ ⥛ ⥜ ⥝ ⥞ ⥟ ⥠ ⥡ ⥢ ⥣ ⥤ ⥥ ⥦ ⥧ ⥨ ⥩ ⥪ ⥫ ⥬ ⥭ ⥮ ⥯ ⥰ ⥱ ⥲ ⥳ ⥴ ⥵ ⥶ ⥷ ⥸ ⥹ ⥺ ⥻ ⥼ ⥽ ⥾ ⥿
Miscellaneous Mathematical Symbols-B
⦀ ⦁ ⦂ ⦃ ⦄ ⦅ ⦆ ⦇ ⦈ ⦉ ⦊ ⦋ ⦌ ⦍ ⦎ ⦏ ⦐ ⦑ ⦒ ⦓ ⦔ ⦕ ⦖ ⦗ ⦘ ⦙ ⦚ ⦛ ⦜ ⦝ ⦞ ⦟ ⦠ ⦡ ⦢ ⦣ ⦤ ⦥ ⦦ ⦧ ⦨ ⦩ ⦪ ⦫ ⦬ ⦭ ⦮ ⦯ ⦰ ⦱ ⦲ ⦳ ⦴ ⦵ ⦶ ⦷ ⦸ ⦹ ⦺ ⦻ ⦼ ⦽ ⦾ ⦿ ⧀ ⧁ ⧂ ⧃ ⧄ ⧅ ⧆ ⧇ ⧈ ⧉ ⧊ ⧋ ⧌ ⧍ ⧎ ⧏ ⧐ ⧑ ⧒ ⧓ ⧔ ⧕ ⧖ ⧗ ⧘ ⧙ ⧚ ⧛ ⧜ ⧝ ⧞ ⧟ ⧠ ⧡ ⧢ ⧣ ⧤ ⧥ ⧦ ⧧ ⧨ ⧩ ⧪ ⧫ ⧬ ⧭ ⧮ ⧯ ⧰ ⧱ ⧲ ⧳ ⧴ ⧶ ⧷ ⧺ ⧻ ⧼ ⧽ ⧾ ⧿
Supplemental Mathematical Operators
⨀ ⨁ ⨂ ⨃ ⨄ ⨅ ⨆ ⨇ ⨈ ⨉ ⨊ ⨋ ⨌ ⨍ ⨎ ⨏ ⨐ ⨑ ⨒ ⨓ ⨔ ⨕ ⨖ ⨗ ⨘ ⨙ ⨚ ⨛ ⨜ ⨝ ⨞ ⨟ ⨠ ⨡ ⨢ ⨣ ⨤ ⨥ ⨦ ⨧ ⨨ ⨩ ⨪ ⨫ ⨬ ⨭ ⨮ ⨰ ⨱ ⨲ ⨳ ⨴ ⨵ ⨶ ⨷ ⨸ ⨹ ⨺ ⨻ ⨼ ⨽ ⨾ ⨿ ⩀ ⩁ ⩂ ⩃ ⩄ ⩅ ⩆ ⩇ ⩈ ⩉ ⩊ ⩋ ⩌ ⩍ ⩎ ⩏ ⩐ ⩑ ⩒ ⩓ ⩔ ⩕ ⩖ ⩗ ⩘ ⩙ ⩚ ⩛ ⩜ ⩝ ⩞ ⩟ ⩠ ⩡ ⩢ ⩣ ⩤ ⩥ ⩦ ⩧ ⩨ ⩩ ⩪ ⩫ ⩬ ⩭ ⩮ ⩯ ⩰ ⩱ ⩲ ⩳ ⩴ ⩵ ⩶ ⩷ ⩸ ⩹ ⩺ ⩻ ⩼ ⩽ ⩾ ⩿ ...
CJK Radicals Supplement
⺀ ⺁ ⺂ ⺃ ⺄ ⺅ ⺆ ⺇ ⺈ ⺉ ⺊ ⺋ ⺌ ⺍ ⺎ ⺏ ⺐ ⺑ ⺒ ⺓ ⺔ ⺕ ⺖ ⺗ ⺘ ⺙ ⺛ ⺜ ⺝ ⺞ ⺟ ⺠ ⺡ ⺢ ⺣ ⺤ ⺥ ⺦ ⺧ ⺨ ⺩ ⺪ ⺫ ⺬ ⺭ ⺮ ⺯ ⺰ ⺱ ⺲ ⺳ ⺴ ⺵ ⺶ ⺷ ⺸ ⺹ ⺺ ⺻ ⺼ ⺽ ⺾ ⺿ ⻀ ⻁ ⻂ ⻃ ⻄ ⻅ ⻆ ⻇ ⻈ ⻉ ⻊ ⻋ ⻌ ⻍ ⻎ ⻏ ⻐ ⻑ ⻒ ⻓ ⻔ ⻕ ⻖ ⻗ ⻘ ⻙ ⻚ ⻛ ⻜ ⻝ ⻞ ⻟ ⻠ ⻡ ⻢ ⻣ ⻤ ⻥ ⻦ ⻧ ⻨ ⻩ ⻪ ⻫ ⻬ ⻭ ⻮ ⻯ ⻰ ⻱ ⻲ ⻳
Kangxi Radicals
⼀ ⼁ ⼄ ⼅ ⼆ ⼇ ⼈ ⼉ ⼊ ⼋ ⼌ ⼍ ⼎ ⼏ ⼐ ⼑ ⼒ ⼓ ⼔ ⼕ ⼖ ⼗ ⼘ ⼙ ⼚ ⼛ ⼜ ⼝ ⼞ ⼟ ⼠ ⼡ ⼢ ⼣ ⼤ ⼥ ⼦ ⼧ ⼨ ⼩ ⼪ ⼫ ⼬ ⼭ ⼮ ⼯ ⼰ ⼱ ⼲ ⼳ ⼴ ⼵ ⼶ ⼷ ⼸ ⼹ ⼺ ⼻ ⼼ ⼽ ⼾ ⼿ ⽀ ⽁ ⽂ ⽃ ⽄ ⽅ ⽆ ⽇ ⽈ ⽉ ⽊ ⽋ ⽌ ⽍ ⽎ ⽏ ⽐ ⽑ ⽒ ⽓ ⽔ ⽕ ⽖ ⽗ ⽘ ⽙ ⽚ ⽛ ⽜ ⽝ ⽞ ⽟ ⽠ ⽡ ⽢ ⽣ ⽤ ⽥ ⽦ ⽧ ⽨ ⽩ ⽪ ⽫ ⽬ ⽭ ⽮ ⽯ ⽰ ⽱ ⽲ ⽳ ⽴ ⽵ ⽶ ⽷ ⽸ ⽹ ⽺ ⽻ ⽼ ⽽ ⽾ ⽿ ...
Ideographic Description Characters
⿰ ⿱ ⿲ ⿳ ⿴ ⿵ ⿶ ⿷ ⿸ ⿹ ⿺ ⿻
CJK Symbols and Punctuation
  、 。 〃 〄 々 〆 〈 〉 《 》 「 」 『 』 【 】 〒 〓 〖 〗 〘 〙 〚 〛 〜 〝 〞 〟 〠 〡 〢 〣 〤 〥 〦 〧 〨 〩 〪 〫 〬 〭 〮 〯 〰 〱 〲 〴 〵 〶 〷 〸 〹 〺 〻 〼 〽 〾 〿
Hiragana
ぁ あ ぃ い ぅ う ぇ え ぉ お か が き ぎ く ぐ け げ こ ご さ ざ し じ す ず せ ぜ そ ぞ た だ ち ぢ っ つ づ て で と ど な に ぬ ね の は ば ぱ ひ び ぴ ふ ぶ ぷ へ べ ぺ ほ ぼ ぽ ま み む め も ゃ や ゅ ゆ ょ よ ら り る れ ろ ゎ わ ゐ ゑ を ん ゔ ゕ ゖ ゙ ゚ ゛ ゜ ゝ ゞ ゟ
Katakana
ァ ア ィ イ ゥ ウ ェ エ ォ オ カ ガ キ ギ ク グ ケ ゲ コ ゴ サ ザ シ ジ ス ズ セ ゼ ソ ゾ タ ダ チ ヂ ッ ツ ヅ テ デ ト ド ナ ニ ヌ ネ ハ バ パ ヒ ビ ピ フ ブ プ ヘ ベ ペ ホ ボ ポ マ ミ ム メ モ ャ ヤ ュ ユ ョ ヨ ラ リ ル レ ロ ヮ ワ ヰ ヱ ヲ ン ヴ ヵ ヶ ヷ ヸ ヹ ヺ ・ ー ヽ ヾ ヿ
Bopomofo
ㄅ ㄆ ㄇ ㄈ ㄉ ㄊ ㄋ ㄌ ㄍ ㄎ ㄏ ㄐ ㄑ ㄒ ㄓ ㄔ ㄕ ㄖ ㄗ ㄘ ㄙ ㄚ ㄛ ㄜ ㄝ ㄞ ㄟ ㄠ ㄡ ㄢ ㄣ ㄤ ㄥ ㄦ ㄧ ㄨ ㄩ ㄪ ㄫ ㄬ
Hangul Compatibility Jamo
ㄱ ㄲ ㄳ ㄴ ㄵ ㄶ ㄷ ㄸ ㄹ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅁ ㅂ ㅃ ㅄ ㅅ ㅆ ㅇ ㅈ ㅉ ㅊ ㅋ ㅌ ㅍ ㅎ ㅏ ㅐ ㅑ ㅒ ㅓ ㅔ ㅕ ㅖ ㅗ ㅘ ㅙ ㅚ ㅛ ㅜ ㅝ ㅞ ㅟ ㅠ ㅡ ㅢ ㅣ ㅥ ㅦ ㅧ ㅨ ㅩ ㅪ ㅫ ㅬ ㅭ ㅮ ㅯ ㅰ ㅱ ㅲ ㅳ ㅴ ㅵ ㅶ ㅷ ㅸ ㅹ ㅺ ㅻ ㅼ ㅽ ㅾ ㅿ ㆀ ㆁ ㆂ ㆃ ㆄ ㆅ ㆆ ㆇ ㆈ ㆉ ㆊ ㆋ ㆌ ㆍ ㆎ
Kanbun
㆐ ㆑ ㆒ ㆓ ㆔ ㆕ ㆖ ㆗ ㆘ ㆙ ㆚ ㆛ ㆜ ㆝ ㆞ ㆟
Bopomofo Extended
ㆠ ㆡ ㆢ ㆣ ㆤ ㆥ ㆦ ㆧ ㆨ ㆩ ㆪ ㆫ ㆬ ㆭ ㆮ ㆯ ㆰ ㆱ ㆲ ㆳ ㆴ ㆵ ㆶ ㆷ
Katakana Phonetic Extensions
ㇰ ㇱ ㇲ ㇳ ㇴ ㇵ ㇶ ㇷ ㇸ ㇹ ㇺ ㇻ ㇼ ㇽ ㇾ ㇿ
Enclosed CJK Letters and Months
㈀ ㈁ ㈂ ㈃ ㈄ ㈅ ㈆ ㈇ ㈈ ㈉ ㈊ ㈋ ㈌ ㈍ ㈎ ㈏ ㈐ ㈑ ㈒ ㈓ ㈔ ㈕ ㈖ ㈗ ㈘ ㈙ ㈚ ㈛ ㈜ ㈠ ㈡ ㈢ ㈣ ㈤ ㈥ ㈦ ㈧ ㈨ ㈩ ㈪ ㈫ ㈬ ㈭ ㈮ ㈯ ㈰ ㈱ ㈲ ㈳ ㈴ ㈵ ㈶ ㈷ ㈸ ㈹ ㈺ ㈻ ㈼ ㈽ ㈾ ㈿ ㉀ ㉁ ㉂ ㉃ ㉑ ㉒ ㉓ ㉔ ㉕ ㉖ ㉗ ㉘ ㉙ ㉚ ㉛ ㉜ ㉝ ㉞ ㉟ ㉠ ㉡ ㉢ ㉣ ㉤ ㉥ ㉦ ㉧ ㉨ ㉩ ㉪ ㉫ ㉬ ㉭ ㉮ ㉯ ㉰ ㉱ ㉲ ㉳ ㉴ ㉵ ㉶ ㉷ ㉸ ㉹ ㉺ ㉻ ㉿ ㊀ ㊁ ㊂ ㊃ ㊄ ㊅ ㊆ ㊇ ㊈ ㊉ ㊊ ㊋ ㊌ ㊍ ㊎ ㊏ ㊐ ㊑ ㊒ ...
CJK Compatibility
㌀ ㌁ ㌂ ㌃ ㌄ ㌅ ㌆ ㌇ ㌈ ㌉ ㌊ ㌋ ㌌ ㌍ ㌎ ㌏ ㌐ ㌑ ㌒ ㌓ ㌔ ㌕ ㌖ ㌗ ㌘ ㌙ ㌚ ㌛ ㌜ ㌝ ㌞ ㌟ ㌠ ㌡ ㌢ ㌣ ㌤ ㌥ ㌦ ㌧ ㌨ ㌩ ㌪ ㌫ ㌬ ㌭ ㌮ ㌯ ㌰ ㌱ ㌲ ㌳ ㌴ ㌵ ㌶ ㌷ ㌸ ㌹ ㌺ ㌻ ㌼ ㌽ ㌾ ㌿ ㍀ ㍁ ㍂ ㍃ ㍄ ㍅ ㍆ ㍇ ㍈ ㍉ ㍊ ㍋ ㍌ ㍍ ㍎ ㍏ ㍐ ㍑ ㍒ ㍓ ㍔ ㍕ ㍖ ㍗ ㍘ ㍙ ㍚ ㍛ ㍜ ㍝ ㍞ ㍟ ㍠ ㍡ ㍢ ㍣ ㍤ ㍥ ㍦ ㍧ ㍨ ㍩ ㍪ ㍫ ㍬ ㍭ ㍮ ㍯ ㍰ ㍱ ㍲ ㍳ ㍴ ㍵ ㍶ ㍻ ㍼ ㍽ ㍾ ㍿ ㎀ ㎁ ㎂ ㎃ ...
CJK Unified Ideographs Extension A
㐀 㐁 㐂 㐃 㐄 㐅 㐆 㐇 㐈 㐉 㐊 㐋 㐌 㐍 㐎 㐏 㐐 㐑 㐒 㐓 㐔 㐕 㐖 㐗 㐘 㐙 㐚 㐛 㐜 㐝 㐞 㐟 㐠 㐡 㐢 㐣 㐤 㐥 㐦 㐧 㐨 㐩 㐪 㐫 㐬 㐭 㐮 㐯 㐰 㐱 㐲 㐳 㐴 㐵 㐶 㐷 㐸 㐹 㐺 㐻 㐼 㐽 㐾 㐿 㑀 㑁 㑂 㑃 㑄 㑅 㑆 㑇 㑈 㑉 㑊 㑋 㑌 㑍 㑎 㑏 㑐 㑑 㑒 㑓 㑔 㑕 㑖 㑗 㑘 㑙 㑚 㑛 㑜 㑝 㑞 㑟 㑠 㑡 㑢 㑣 㑤 㑥 㑦 㑧 㑨 㑩 㑪 㑫 㑬 㑭 㑮 㑯 㑰 㑱 㑲 㑳 㑴 㑵 㑶 㑷 㑸 㑹 㑺 㑻 㑼 㑽 㑾 㑿 ...
CJK Unified Ideographs
一 丁 丂 七 丄 丅 丆 万 丈 三 上 下 丌 不 与 丏 丐 丑 丒 专 且 丕 世 丗 丘 丙 业 丛 东 丝 丞 丟 丠 両 丢 丣 两 严 並 丧 丨 丩 个 丫 丬 中 丮 丯 丰 丱 串 丳 临 丵 丷 丸 丹 为 主 丼 丽 举 丿 乀 乁 乂 乃 乄 久 乆 乇 么 义 乊 之 乌 乍 乎 乏 乐 乑 乒 乓 乔 乕 乖 乗 乘 乙 乚 乛 乜 九 乞 也 习 乡 乢 乣 乤 乥 书 乧 乨 乩 乪 乫 乬 乭 乮 乯 买 乱 乲 乳 乴 乵 乶 乷 乸 乹 乺 乻 乼 乽 乾 乿 ...
Yi Syllables
ꀀ ꀁ ꀂ ꀃ ꀄ ꀅ ꀆ ꀇ ꀈ ꀉ ꀊ ꀋ ꀌ ꀍ ꀎ ꀏ ꀐ ꀑ ꀒ ꀓ ꀔ ꀕ ꀖ ꀗ ꀘ ꀙ ꀚ ꀛ ꀜ ꀝ ꀞ ꀟ ꀠ ꀡ ꀢ ꀣ ꀤ ꀥ ꀦ ꀧ ꀨ ꀩ ꀪ ꀫ ꀬ ꀭ ꀮ ꀯ ꀰ ꀱ ꀲ ꀳ ꀴ ꀵ ꀶ ꀷ ꀸ ꀹ ꀺ ꀻ ꀼ ꀽ ꀾ ꀿ ꁀ ꁁ ꁂ ꁃ ꁄ ꁅ ꁆ ꁇ ꁈ ꁉ ꁊ ꁋ ꁌ ꁍ ꁎ ꁏ ꁐ ꁑ ꁒ ꁓ ꁔ ꁕ ꁖ ꁗ ꁘ ꁙ ꁚ ꁛ ꁜ ꁝ ꁞ ꁟ ꁠ ꁡ ꁢ ꁣ ꁤ ꁥ ꁦ ꁧ ꁨ ꁩ ꁪ ꁫ ꁬ ꁭ ꁮ ꁯ ꁰ ꁱ ꁲ ꁳ ꁴ ꁵ ꁶ ꁷ ꁸ ꁹ ꁺ ꁻ ꁼ ꁽ ꁾ ꁿ ...
Yi Radicals
꒐ ꒑ ꒒ ꒓ ꒔ ꒕ ꒖ ꒗ ꒘ ꒙ ꒚ ꒛ ꒜ ꒝ ꒞ ꒟ ꒠ ꒡ ꒢ ꒣ ꒤ ꒥ ꒦ ꒧ ꒨ ꒩ ꒪ ꒫ ꒬ ꒭ ꒮ ꒯ ꒰ ꒱ ꒲ ꒳ ꒴ ꒵ ꒶ ꒷ ꒸ ꒹ ꒺ ꒻ ꒼ ꒽ ꒾ ꒿ ꓀ ꓁ ꓂ ꓃ ꓄ ꓅ ꓆
Hangul Syllables
가 각 갂 갃 간 갅 갆 갇 갈 갉 갊 갋 갌 갍 갎 갏 감 갑 값 갓 갔 강 갖 갗 갘 같 갚 갛 개 객 갞 갟 갠 갡 갢 갣 갤 갥 갦 갧 갨 갩 갪 갫 갬 갭 갮 갯 갰 갱 갲 갳 갴 갵 갶 갷 갸 갹 갺 갻 갼 갽 갾 갿 걀 걁 걂 걃 걄 걅 걆 걇 걈 걉 걊 걋 걌 걍 걎 걏 걐 걑 걒 걓 걔 걕 걖 걗 걘 걙 걚 걛 걜 걝 걞 걟 걠 걡 걢 걣 걤 걥 걦 걧 걨 걩 걪 걫 걬 걭 걮 걯 거 걱 걲 걳 건 걵 걶 걷 걸 걹 걺 걻 걼 걽 걾 걿 ...
Private Use Area
                                                                                                                                ...
CJK Compatibility Ideographs
豈 更 車 賈 滑 串 句 龜 龜 契 金 喇 奈 懶 癩 羅 蘿 螺 裸 邏 樂 洛 烙 珞 落 酪 駱 亂 卵 欄 爛 蘭 鸞 嵐 濫 藍 襤 拉 臘 蠟 廊 朗 浪 狼 郎 來 冷 勞 擄 櫓 爐 盧 老 蘆 虜 路 露 魯 鷺 碌 祿 綠 菉 錄 鹿 論 壟 弄 籠 聾 牢 磊 賂 雷 壘 屢 樓 淚 漏 累 縷 陋 勒 肋 凜 凌 稜 綾 菱 陵 讀 拏 樂 諾 丹 寧 怒 率 異 北 磻 便 復 不 泌 數 索 參 塞 省 葉 說 殺 辰 沈 拾 若 掠 略 亮 兩 凉 梁 糧 良 諒 量 勵 ...
Alphabetic Presentation Forms
ff fi fl ffi ffl ſt st ﬓ ﬔ ﬕ ﬖ ﬗ יִ ﬞ ײַ ﬠ ﬡ ﬢ ﬣ ﬤ ﬥ ﬦ ﬧ ﬨ ﬩ שׁ שׂ שּׁ שּׂ אַ אָ אּ בּ גּ דּ הּ וּ זּ טּ יּ ךּ כּ לּ מּ נּ סּ ףּ פּ צּ קּ רּ שּ תּ וֹ בֿ כֿ פֿ ﭏ
Arabic Presentation Forms-A
ﭐ ﭑ ﭒ ﭓ ﭔ ﭕ ﭖ ﭗ ﭘ ﭙ ﭚ ﭛ ﭜ ﭝ ﭞ ﭟ ﭠ ﭡ ﭢ ﭣ ﭤ ﭥ ﭦ ﭧ ﭨ ﭩ ﭪ ﭫ ﭬ ﭭ ﭮ ﭯ ﭰ ﭱ ﭲ ﭳ ﭴ ﭵ ﭶ ﭷ ﭸ ﭹ ﭺ ﭻ ﭼ ﭽ ﭾ ﭿ ﮀ ﮁ ﮂ ﮃ ﮄ ﮅ ﮆ ﮇ ﮈ ﮉ ﮊ ﮋ ﮌ ﮍ ﮎ ﮏ ﮐ ﮑ ﮒ ﮓ ﮔ ﮕ ﮖ ﮗ ﮘ ﮙ ﮚ ﮛ ﮜ ﮝ ﮞ ﮟ ﮠ ﮡ ﮢ ﮣ ﮤ ﮥ ﮮ ﮯ ﮰ ﮱ ﯓ ﯔ ﯕ ﯖ ﯗ ﯘ ﯙ ﯚ ﯛ ﯜ ﯝ ﯞ ﯟ ﯠ ﯡ ﯢ ﯣ ﯤ ﯥ ﯦ ﯧ ﯨ ﯩ ﯪ ﯫ ﯬ ﯭ ﯮ ﯯ ﯰ ...
Variation Selectors
Combining Half Marks
︠ ︡ ︢ ︣
CJK Compatibility Forms
︱ ︲ ︳ ︴ ︵ ︶ ︷ ︸ ︹ ︺ ︻ ︼ ︽ ︾ ︿ ﹀ ﹁ ﹂ ﹃ ﹄ ﹅ ﹆ ﹉ ﹊ ﹋ ﹌
Small Form Variants
﹐ ﹑ ﹒ ﹔ ﹕ ﹖ ﹗ ﹙ ﹚ ﹛ ﹜ ﹝ ﹞ ﹟ ﹠ ﹡ ﹢ ﹣ ﹤ ﹥ ﹦ ﹩ ﹪ ﹫
Arabic Presentation Forms-B
ﹰ ﹱ ﹲ ﹳ ﹴ ﹶ ﹷ ﹸ ﹹ ﹺ ﹻ ﹼ ﹽ ﹾ ﹿ ﺀ ﺁ ﺂ ﺃ ﺄ ﺅ ﺆ ﺇ ﺈ ﺉ ﺊ ﺋ ﺌ ﺏ ﺐ ﺑ ﺒ ﺓ ﺔ ﺕ ﺖ ﺗ ﺘ ﺙ ﺚ ﺛ ﺜ ﺝ ﺞ ﺟ ﺠ ﺡ ﺢ ﺣ ﺤ ﺥ ﺦ ﺧ ﺨ ﺩ ﺪ ﺫ ﺬ ﺭ ﺮ ﺯ ﺰ ﺱ ﺲ ﺳ ﺴ ﺵ ﺶ ﺷ ﺸ ﺹ ﺺ ﺻ ﺼ ﺽ ﺾ ﺿ ﻀ ﻁ ﻂ ﻃ ﻄ ﻅ ﻆ ﻇ ﻈ ﻉ ﻊ ﻋ ﻌ ﻍ ﻎ ﻏ ﻐ ﻑ ﻒ ﻓ ﻔ ﻕ ﻖ ﻗ ﻘ ﻙ ﻚ ﻛ ﻜ ﻝ ﻞ ﻟ ﻠ ﻡ ﻢ ﻣ ﻤ ﻥ ﻦ ﻧ ﻨ ﻭ ﻮ ﻯ ﻰ ...
Halfwidth and Fullwidth Forms
_ ⦅ ⦆ 。 「 」 、 ・ ヲ ァ ィ ゥ ェ ォ ャ ュ ョ ッ ー ア イ ウ エ オ カ キ ク ケ コ サ シ ス セ ソ タ ...
Specials
   <20>
Old Italic
𐌀 𐌁 𐌂 𐌃 𐌄 𐌅 𐌆 𐌇 𐌈 𐌉 𐌊 𐌋 𐌌 𐌍 𐌎 𐌏 𐌐 𐌑 𐌒 𐌓 𐌔 𐌕 𐌖 𐌗 𐌘 𐌙 𐌚 𐌛 𐌜 𐌝 𐌞 𐌠 𐌡 𐌢 𐌣
Gothic
𐌰 𐌱 𐌲 𐌳 𐌴 𐌵 𐌶 𐌷 𐌸 𐌹 𐌺 𐌻 𐌼 𐌽 𐌾 𐌿 𐍀 𐍁 𐍂 𐍃 𐍄 𐍅 𐍆 𐍇 𐍈 𐍉 𐍊
Deseret
𐐀 𐐁 𐐂 𐐃 𐐄 𐐅 𐐆 𐐇 𐐈 𐐉 𐐊 𐐋 𐐌 𐐍 𐐎 𐐏 𐐐 𐐑 𐐒 𐐓 𐐔 𐐕 𐐖 𐐗 𐐘 𐐙 𐐚 𐐛 𐐜 𐐝 𐐞 𐐟 𐐠 𐐡 𐐢 𐐣 𐐤 𐐥 𐐨 𐐩 𐐪 𐐫 𐐬 𐐭 𐐮 𐐯 𐐰 𐐱 𐐲 𐐳 𐐴 𐐵 𐐶 𐐷 𐐸 𐐹 𐐺 𐐻 𐐼 𐐽 𐐾 𐐿 𐑀 𐑁 𐑂 𐑃 𐑄 𐑅 𐑆 𐑇 𐑈 𐑉 𐑊 𐑋 𐑌 𐑍
Byzantine Musical Symbols
𝀀 𝀁 𝀂 𝀃 𝀄 𝀅 𝀆 𝀇 𝀈 𝀉 𝀊 𝀋 𝀌 𝀍 𝀎 𝀏 𝀐 𝀑 𝀒 𝀓 𝀔 𝀕 𝀖 𝀗 𝀘 𝀙 𝀚 𝀛 𝀜 𝀝 𝀞 𝀟 𝀠 𝀡 𝀢 𝀣 𝀤 𝀥 𝀦 𝀧 𝀨 𝀩 𝀪 𝀫 𝀬 𝀭 𝀮 𝀯 𝀰 𝀱 𝀲 𝀳 𝀴 𝀵 𝀶 𝀷 𝀸 𝀹 𝀺 𝀻 𝀼 𝀽 𝀾 𝀿 𝁀 𝁁 𝁂 𝁃 𝁄 𝁅 𝁆 𝁇 𝁈 𝁉 𝁊 𝁋 𝁌 𝁍 𝁎 𝁏 𝁐 𝁑 𝁒 𝁓 𝁔 𝁕 𝁖 𝁗 𝁘 𝁙 𝁚 𝁛 𝁜 𝁝 𝁞 𝁟 𝁠 𝁡 𝁢 𝁣 𝁤 𝁥 𝁦 𝁧 𝁨 𝁩 𝁪 𝁫 𝁬 𝁭 𝁮 𝁯 𝁰 𝁱 𝁲 𝁳 𝁴 𝁵 𝁶 𝁷 𝁸 𝁹 𝁺 𝁻 𝁼 𝁽 𝁾 𝁿 ...
Musical Symbols
𝄀 𝄁 𝄂 𝄃 𝄄 𝄅 𝄆 𝄇 𝄈 𝄉 𝄊 𝄋 𝄌 𝄍 𝄎 𝄏 𝄐 𝄑 𝄒 𝄓 𝄔 𝄕 𝄖 𝄗 𝄘 𝄙 𝄚 𝄛 𝄜 𝄝 𝄞 𝄟 𝄠 𝄡 𝄢 𝄣 𝄤 𝄥 𝄦 𝄪 𝄫 𝄬 𝄭 𝄮 𝄯 𝄰 𝄱 𝄲 𝄳 𝄴 𝄵 𝄶 𝄷 𝄸 𝄹 𝄺 𝄻 𝄼 𝄽 𝄾 𝄿 𝅀 𝅁 𝅂 𝅃 𝅄 𝅅 𝅆 𝅇 𝅈 𝅉 𝅊 𝅋 𝅌 𝅍 𝅎 𝅏 𝅐 𝅑 𝅒 𝅓 𝅔 𝅕 𝅖 𝅗 𝅘 𝅙 𝅚 𝅛 𝅜 𝅝 𝅗𝅥 𝅘𝅥 𝅘𝅥𝅮 𝅘𝅥𝅯 𝅘𝅥𝅰 𝅘𝅥𝅱 𝅘𝅥𝅲 𝅥 𝅦 𝅧 𝅨 𝅩 𝅪 𝅫 𝅬 𝅭 𝅮 𝅯 𝅰 𝅱 𝅲 𝅳 𝅴 𝅵 𝅶 𝅷 𝅸 𝅹 𝅺 𝅻 𝅼 𝅽 𝅾 𝅿 𝆀 𝆁 𝆂 ...
Mathematical Alphanumeric Symbols
𝐀 𝐁 𝐂 𝐃 𝐄 𝐅 𝐆 𝐇 𝐈 𝐉 𝐊 𝐋 𝐌 𝐍 𝐎 𝐏 𝐐 𝐑 𝐒 𝐓 𝐔 𝐕 𝐖 𝐗 𝐘 𝐙 𝐚 𝐛 𝐜 𝐝 𝐞 𝐟 𝐠 𝐡 𝐢 𝐣 𝐤 𝐥 𝐦 𝐧 𝐨 𝐩 𝐪 𝐫 𝐬 𝐭 𝐮 𝐯 𝐰 𝐱 𝐲 𝐳 𝐴 𝐵 𝐶 𝐷 𝐸 𝐹 𝐺 𝐻 𝐼 𝐽 𝐾 𝐿 𝑀 𝑁 𝑂 𝑃 𝑄 𝑅 𝑆 𝑇 𝑈 𝑉 𝑊 𝑋 𝑌 𝑍 𝑎 𝑏 𝑐 𝑑 𝑒 𝑓 𝑔 𝑖 𝑗 𝑘 𝑙 𝑚 𝑛 𝑜 𝑝 𝑞 𝑟 𝑠 𝑡 𝑢 𝑣 𝑤 𝑥 𝑦 𝑧 𝑨 𝑩 𝑪 𝑫 𝑬 𝑭 𝑮 𝑯 𝑰 𝑱 𝑲 𝑳 𝑴 𝑵 𝑶 𝑷 𝑸 𝑹 𝑺 𝑻 𝑼 𝑽 𝑾 𝑿 𝒀 ...
CJK Unified Ideographs Extension B
𠀀 𠀁 𠀂 𠀃 𠀄 𠀅 𠀆 𠀇 𠀈 𠀉 𠀊 𠀋 𠀌 𠀍 𠀎 𠀏 𠀐 𠀑 𠀒 𠀓 𠀔 𠀕 𠀖 𠀗 𠀘 𠀙 𠀚 𠀛 𠀜 𠀝 𠀞 𠀟 𠀠 𠀡 𠀢 𠀣 𠀤 𠀥 𠀦 𠀧 𠀨 𠀩 𠀪 𠀫 𠀬 𠀭 𠀮 𠀯 𠀰 𠀱 𠀲 𠀳 𠀴 𠀵 𠀶 𠀷 𠀸 𠀹 𠀺 𠀻 𠀼 𠀽 𠀾 𠀿 𠁀 𠁁 𠁂 𠁃 𠁄 𠁅 𠁆 𠁇 𠁈 𠁉 𠁊 𠁋 𠁌 𠁍 𠁎 𠁏 𠁐 𠁑 𠁒 𠁓 𠁔 𠁕 𠁖 𠁗 𠁘 𠁙 𠁚 𠁛 𠁜 𠁝 𠁞 𠁟 𠁠 𠁡 𠁢 𠁣 𠁤 𠁥 𠁦 𠁧 𠁨 𠁩 𠁪 𠁫 𠁬 𠁭 𠁮 𠁯 𠁰 𠁱 𠁲 𠁳 𠁴 𠁵 𠁶 𠁷 𠁸 𠁹 𠁺 𠁻 𠁼 𠁽 𠁾 𠁿 ...
CJK Compatibility Ideographs Supplement
丽 丸 乁 𠄢 你 侮 侻 倂 偺 備 僧 像 㒞 𠘺 免 兔 兤 具 𠔜 㒹 內 再 𠕋 冗 冤 仌 冬 况 𩇟 凵 刃 㓟 刻 剆 割 剷 㔕 勇 勉 勤 勺 包 匆 北 卉 卑 博 即 卽 卿 卿 卿 𠨬 灰 及 叟 𠭣 叫 叱 吆 咞 吸 呈 周 咢 哶 唐 啓 啣 善 善 喙 喫 喳 嗂 圖 嘆 圗 噑 噴 切 壮 城 埴 堍 型 堲 報 墬 𡓤 売 壷 夆 多 夢 奢 𡚨 𡛪 姬 娛 娧 姘 婦 㛮 㛼 嬈 嬾 嬾 𡧈 寃 寘 寧 寳 𡬘 寿 将 当 尢 㞁 屠 屮 峀 岍 𡷤 嵃 𡷦 嵮 嵫 ...
Tags
󠀁 󠀠 󠀡 󠀢 󠀣 󠀤 󠀥 󠀦 󠀧 󠀨 󠀩 󠀪 󠀫 󠀬 󠀭 󠀮 󠀯 󠀰 󠀱 󠀲 󠀳 󠀴 󠀵 󠀶 󠀷 󠀸 󠀹 󠀺 󠀻 󠀼 󠀽 󠀾 󠀿 󠁀 󠁁 󠁂 󠁃 󠁄 󠁅 󠁆 󠁇 󠁈 󠁉 󠁊 󠁋 󠁌 󠁍 󠁎 󠁏 󠁐 󠁑 󠁒 󠁓 󠁔 󠁕 󠁖 󠁗 󠁘 󠁙 󠁚 󠁛 󠁜 󠁝 󠁞 󠁟 󠁠 󠁡 󠁢 󠁣 󠁤 󠁥 󠁦 󠁧 󠁨 󠁩 󠁪 󠁫 󠁬 󠁭 󠁮 󠁯 󠁰 󠁱 󠁲 󠁳 󠁴 󠁵 󠁶 󠁷 󠁸 󠁹 󠁺 󠁻 󠁼 󠁽 󠁾 󠁿
Supplementary Private Use Area-A
󰀀 󰀁 󰀂 󰀃 󰀄 󰀅 󰀆 󰀇 󰀈 󰀉 󰀊 󰀋 󰀌 󰀍 󰀎 󰀏 󰀐 󰀑 󰀒 󰀓 󰀔 󰀕 󰀖 󰀗 󰀘 󰀙 󰀚 󰀛 󰀜 󰀝 󰀞 󰀟 󰀠 󰀡 󰀢 󰀣 󰀤 󰀥 󰀦 󰀧 󰀨 󰀩 󰀪 󰀫 󰀬 󰀭 󰀮 󰀯 󰀰 󰀱 󰀲 󰀳 󰀴 󰀵 󰀶 󰀷 󰀸 󰀹 󰀺 󰀻 󰀼 󰀽 󰀾 󰀿 󰁀 󰁁 󰁂 󰁃 󰁄 󰁅 󰁆 󰁇 󰁈 󰁉 󰁊 󰁋 󰁌 󰁍 󰁎 󰁏 󰁐 󰁑 󰁒 󰁓 󰁔 󰁕 󰁖 󰁗 󰁘 󰁙 󰁚 󰁛 󰁜 󰁝 󰁞 󰁟 󰁠 󰁡 󰁢 󰁣 󰁤 󰁥 󰁦 󰁧 󰁨 󰁩 󰁪 󰁫 󰁬 󰁭 󰁮 󰁯 󰁰 󰁱 󰁲 󰁳 󰁴 󰁵 󰁶 󰁷 󰁸 󰁹 󰁺 󰁻 󰁼 󰁽 󰁾 󰁿 ...
Supplementary Private Use Area-B
􀀀 􀀁 􀀂 􀀃 􀀄 􀀅 􀀆 􀀇 􀀈 􀀉 􀀊 􀀋 􀀌 􀀍 􀀎 􀀏 􀀐 􀀑 􀀒 􀀓 􀀔 􀀕 􀀖 􀀗 􀀘 􀀙 􀀚 􀀛 􀀜 􀀝 􀀞 􀀟 􀀠 􀀡 􀀢 􀀣 􀀤 􀀥 􀀦 􀀧 􀀨 􀀩 􀀪 􀀫 􀀬 􀀭 􀀮 􀀯 􀀰 􀀱 􀀲 􀀳 􀀴 􀀵 􀀶 􀀷 􀀸 􀀹 􀀺 􀀻 􀀼 􀀽 􀀾 􀀿 􀁀 􀁁 􀁂 􀁃 􀁄 􀁅 􀁆 􀁇 􀁈 􀁉 􀁊 􀁋 􀁌 􀁍 􀁎 􀁏 􀁐 􀁑 􀁒 􀁓 􀁔 􀁕 􀁖 􀁗 􀁘 􀁙 􀁚 􀁛 􀁜 􀁝 􀁞 􀁟 􀁠 􀁡 􀁢 􀁣 􀁤 􀁥 􀁦 􀁧 􀁨 􀁩 􀁪 􀁫 􀁬 􀁭 􀁮 􀁯 􀁰 􀁱 􀁲 􀁳 􀁴 􀁵 􀁶 􀁷 􀁸 􀁹 􀁺 􀁻 􀁼 􀁽 􀁾 􀁿 ...

5
vendor/cosmic-text/sample/tabs.txt vendored Normal file
View File

@@ -0,0 +1,5 @@
Tabs:
One Two Three Four
Two Three Four
Three Four
Four

3
vendor/cosmic-text/sample/thai.txt vendored Normal file
View File

@@ -0,0 +1,3 @@
ข้อ 1 มนุษย์ทั้งหลายเกิดมามีอิสระและเสมอภาคกันในเกียรติศักด[เกียรติศักดิ์]และสิทธิ ต่างมีเหตุผลและมโนธรรม และควรปฏิบัติต่อกันด้วยเจตนารมณ์แห่งภราดรภาพ
ข้อ 2 ทุกคนย่อมมีสิทธิและอิสรภาพบรรดาที่กำหนดไว้ในปฏิญญานี้ โดยปราศจากความแตกต่างไม่ว่าชนิดใด ๆ ดังเช่น เชื้อชาติ ผิว เพศ ภาษา ศาสนา ความคิดเห็นทางการเมืองหรือทางอื่น เผ่าพันธุ์แห่งชาติ หรือสังคม ทรัพย์สิน กำเนิด หรือสถานะอื่น ๆ อนึ่งจะไม่มีความแตกต่างใด ๆ ตามมูลฐานแห่งสถานะทางการเมือง ทางการศาล หรือทางการระหว่างประเทศของประเทศหรือดินแดนที่บุคคลสังกัด ไม่ว่าดินแดนนี้จะเป็นเอกราช อยู่ในความพิทักษ์มิได้ปกครองตนเอง หรืออยู่ภายใต้การจำกัดอธิปไตยใด ๆ ทั้งสิ้น

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

BIN
vendor/cosmic-text/screenshots/hindi.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

384
vendor/cosmic-text/src/attrs.rs vendored Normal file
View File

@@ -0,0 +1,384 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::ops::Range;
use rangemap::RangeMap;
use smol_str::SmolStr;
use crate::{CacheKeyFlags, Metrics};
pub use fontdb::{Family, Stretch, Style, Weight};
/// Text color
#[derive(Clone, Copy, Debug, PartialOrd, Ord, Eq, Hash, PartialEq)]
pub struct Color(pub u32);
impl Color {
/// Create new color with red, green, and blue components
#[inline]
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self::rgba(r, g, b, 0xFF)
}
/// Create new color with red, green, blue, and alpha components
#[inline]
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
}
/// Get a tuple over all of the attributes, in `(r, g, b, a)` order.
#[inline]
pub fn as_rgba_tuple(self) -> (u8, u8, u8, u8) {
(self.r(), self.g(), self.b(), self.a())
}
/// Get an array over all of the components, in `[r, g, b, a]` order.
#[inline]
pub fn as_rgba(self) -> [u8; 4] {
[self.r(), self.g(), self.b(), self.a()]
}
/// Get the red component
#[inline]
pub fn r(&self) -> u8 {
((self.0 & 0x00_FF_00_00) >> 16) as u8
}
/// Get the green component
#[inline]
pub fn g(&self) -> u8 {
((self.0 & 0x00_00_FF_00) >> 8) as u8
}
/// Get the blue component
#[inline]
pub fn b(&self) -> u8 {
(self.0 & 0x00_00_00_FF) as u8
}
/// Get the alpha component
#[inline]
pub fn a(&self) -> u8 {
((self.0 & 0xFF_00_00_00) >> 24) as u8
}
}
/// An owned version of [`Family`]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum FamilyOwned {
Name(SmolStr),
Serif,
SansSerif,
Cursive,
Fantasy,
Monospace,
}
impl FamilyOwned {
pub fn new(family: Family) -> Self {
match family {
Family::Name(name) => FamilyOwned::Name(SmolStr::from(name)),
Family::Serif => FamilyOwned::Serif,
Family::SansSerif => FamilyOwned::SansSerif,
Family::Cursive => FamilyOwned::Cursive,
Family::Fantasy => FamilyOwned::Fantasy,
Family::Monospace => FamilyOwned::Monospace,
}
}
pub fn as_family(&self) -> Family {
match self {
FamilyOwned::Name(name) => Family::Name(name),
FamilyOwned::Serif => Family::Serif,
FamilyOwned::SansSerif => Family::SansSerif,
FamilyOwned::Cursive => Family::Cursive,
FamilyOwned::Fantasy => Family::Fantasy,
FamilyOwned::Monospace => Family::Monospace,
}
}
}
/// Metrics, but implementing Eq and Hash using u32 representation of f32
//TODO: what are the edge cases of this?
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct CacheMetrics {
font_size_bits: u32,
line_height_bits: u32,
}
impl From<Metrics> for CacheMetrics {
fn from(metrics: Metrics) -> Self {
Self {
font_size_bits: metrics.font_size.to_bits(),
line_height_bits: metrics.line_height.to_bits(),
}
}
}
impl From<CacheMetrics> for Metrics {
fn from(metrics: CacheMetrics) -> Self {
Self {
font_size: f32::from_bits(metrics.font_size_bits),
line_height: f32::from_bits(metrics.line_height_bits),
}
}
}
/// Text attributes
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Attrs<'a> {
//TODO: should this be an option?
pub color_opt: Option<Color>,
pub family: Family<'a>,
pub stretch: Stretch,
pub style: Style,
pub weight: Weight,
pub metadata: usize,
pub cache_key_flags: CacheKeyFlags,
pub metrics_opt: Option<CacheMetrics>,
}
impl<'a> Attrs<'a> {
/// Create a new set of attributes with sane defaults
///
/// This defaults to a regular Sans-Serif font.
pub fn new() -> Self {
Self {
color_opt: None,
family: Family::SansSerif,
stretch: Stretch::Normal,
style: Style::Normal,
weight: Weight::NORMAL,
metadata: 0,
cache_key_flags: CacheKeyFlags::empty(),
metrics_opt: None,
}
}
/// Set [Color]
pub fn color(mut self, color: Color) -> Self {
self.color_opt = Some(color);
self
}
/// Set [Family]
pub fn family(mut self, family: Family<'a>) -> Self {
self.family = family;
self
}
/// Set [Stretch]
pub fn stretch(mut self, stretch: Stretch) -> Self {
self.stretch = stretch;
self
}
/// Set [Style]
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
/// Set [Weight]
pub fn weight(mut self, weight: Weight) -> Self {
self.weight = weight;
self
}
/// Set metadata
pub fn metadata(mut self, metadata: usize) -> Self {
self.metadata = metadata;
self
}
/// Set [`CacheKeyFlags`]
pub fn cache_key_flags(mut self, cache_key_flags: CacheKeyFlags) -> Self {
self.cache_key_flags = cache_key_flags;
self
}
/// Set [`Metrics`], overriding values in buffer
pub fn metrics(mut self, metrics: Metrics) -> Self {
self.metrics_opt = Some(metrics.into());
self
}
/// Check if font matches
pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
//TODO: smarter way of including emoji
face.post_script_name.contains("Emoji")
|| (face.style == self.style && face.stretch == self.stretch)
}
/// Check if this set of attributes can be shaped with another
pub fn compatible(&self, other: &Self) -> bool {
self.family == other.family
&& self.stretch == other.stretch
&& self.style == other.style
&& self.weight == other.weight
}
}
/// Font-specific part of [`Attrs`] to be used for matching
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct FontMatchAttrs {
family: FamilyOwned,
stretch: Stretch,
style: Style,
weight: Weight,
}
impl<'a> From<Attrs<'a>> for FontMatchAttrs {
fn from(attrs: Attrs<'a>) -> Self {
Self {
family: FamilyOwned::new(attrs.family),
stretch: attrs.stretch,
style: attrs.style,
weight: attrs.weight,
}
}
}
/// An owned version of [`Attrs`]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct AttrsOwned {
//TODO: should this be an option?
pub color_opt: Option<Color>,
pub family_owned: FamilyOwned,
pub stretch: Stretch,
pub style: Style,
pub weight: Weight,
pub metadata: usize,
pub cache_key_flags: CacheKeyFlags,
pub metrics_opt: Option<CacheMetrics>,
}
impl AttrsOwned {
pub fn new(attrs: Attrs) -> Self {
Self {
color_opt: attrs.color_opt,
family_owned: FamilyOwned::new(attrs.family),
stretch: attrs.stretch,
style: attrs.style,
weight: attrs.weight,
metadata: attrs.metadata,
cache_key_flags: attrs.cache_key_flags,
metrics_opt: attrs.metrics_opt,
}
}
pub fn as_attrs(&self) -> Attrs {
Attrs {
color_opt: self.color_opt,
family: self.family_owned.as_family(),
stretch: self.stretch,
style: self.style,
weight: self.weight,
metadata: self.metadata,
cache_key_flags: self.cache_key_flags,
metrics_opt: self.metrics_opt,
}
}
}
/// List of text attributes to apply to a line
//TODO: have this clean up the spans when changes are made
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct AttrsList {
defaults: AttrsOwned,
pub(crate) spans: RangeMap<usize, AttrsOwned>,
}
impl AttrsList {
/// Create a new attributes list with a set of default [Attrs]
pub fn new(defaults: Attrs) -> Self {
Self {
defaults: AttrsOwned::new(defaults),
spans: RangeMap::new(),
}
}
/// Get the default [Attrs]
pub fn defaults(&self) -> Attrs {
self.defaults.as_attrs()
}
/// Get the current attribute spans
pub fn spans(&self) -> Vec<(&Range<usize>, &AttrsOwned)> {
self.spans_iter().collect()
}
/// Get an iterator over the current attribute spans
pub fn spans_iter(&self) -> impl Iterator<Item = (&Range<usize>, &AttrsOwned)> + '_ {
self.spans.iter()
}
/// Clear the current attribute spans
pub fn clear_spans(&mut self) {
self.spans.clear();
}
/// Add an attribute span, removes any previous matching parts of spans
pub fn add_span(&mut self, range: Range<usize>, attrs: Attrs) {
//do not support 1..1 or 2..1 even if by accident.
if range.is_empty() {
return;
}
self.spans.insert(range, AttrsOwned::new(attrs));
}
/// Get the attribute span for an index
///
/// This returns a span that contains the index
pub fn get_span(&self, index: usize) -> Attrs {
self.spans
.get(&index)
.map(|v| v.as_attrs())
.unwrap_or(self.defaults.as_attrs())
}
/// Split attributes list at an offset
#[allow(clippy::missing_panics_doc)]
pub fn split_off(&mut self, index: usize) -> Self {
let mut new = Self::new(self.defaults.as_attrs());
let mut removes = Vec::new();
//get the keys we need to remove or fix.
for span in self.spans.iter() {
if span.0.end <= index {
continue;
} else if span.0.start >= index {
removes.push((span.0.clone(), false));
} else {
removes.push((span.0.clone(), true));
}
}
for (key, resize) in removes {
let (range, attrs) = self
.spans
.get_key_value(&key.start)
.map(|v| (v.0.clone(), v.1.clone()))
.expect("attrs span not found");
self.spans.remove(key);
if resize {
new.spans.insert(0..range.end - index, attrs.clone());
self.spans.insert(range.start..index, attrs);
} else {
new.spans
.insert(range.start - index..range.end - index, attrs);
}
}
new
}
/// Resets the attributes with new defaults.
pub(crate) fn reset(mut self, default: Attrs) -> Self {
self.defaults = AttrsOwned::new(default);
self.spans.clear();
self
}
}

40
vendor/cosmic-text/src/bidi_para.rs vendored Normal file
View File

@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use unicode_bidi::{bidi_class, BidiClass, BidiInfo, ParagraphInfo};
/// An iterator over the paragraphs in the input text.
/// It is equivalent to [`core::str::Lines`] but follows `unicode-bidi` behaviour.
#[derive(Debug)]
pub struct BidiParagraphs<'text> {
text: &'text str,
info: alloc::vec::IntoIter<ParagraphInfo>,
}
impl<'text> BidiParagraphs<'text> {
/// Create an iterator to split the input text into paragraphs
/// in accordance with `unicode-bidi` behaviour.
pub fn new(text: &'text str) -> Self {
let info = BidiInfo::new(text, None);
let info = info.paragraphs.into_iter();
Self { text, info }
}
}
impl<'text> Iterator for BidiParagraphs<'text> {
type Item = &'text str;
fn next(&mut self) -> Option<Self::Item> {
let para = self.info.next()?;
let paragraph = &self.text[para.range];
// `para.range` includes the newline that splits the line, so remove it if present
let mut char_indices = paragraph.char_indices();
if let Some(i) = char_indices.next_back().and_then(|(i, c)| {
// `BidiClass::B` is a Paragraph_Separator (various newline characters)
(bidi_class(c) == BidiClass::B).then_some(i)
}) {
Some(&paragraph[0..i])
} else {
Some(paragraph)
}
}
}

1485
vendor/cosmic-text/src/buffer.rs vendored Normal file

File diff suppressed because it is too large Load Diff

310
vendor/cosmic-text/src/buffer_line.rs vendored Normal file
View File

@@ -0,0 +1,310 @@
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::mem;
use crate::{
Align, Attrs, AttrsList, Cached, FontSystem, LayoutLine, LineEnding, ShapeLine, Shaping, Wrap,
};
/// A line (or paragraph) of text that is shaped and laid out
#[derive(Clone, Debug)]
pub struct BufferLine {
text: String,
ending: LineEnding,
attrs_list: AttrsList,
align: Option<Align>,
shape_opt: Cached<ShapeLine>,
layout_opt: Cached<Vec<LayoutLine>>,
shaping: Shaping,
metadata: Option<usize>,
}
impl BufferLine {
/// Create a new line with the given text and attributes list
/// Cached shaping and layout can be done using the [`Self::shape`] and
/// [`Self::layout`] functions
pub fn new<T: Into<String>>(
text: T,
ending: LineEnding,
attrs_list: AttrsList,
shaping: Shaping,
) -> Self {
Self {
text: text.into(),
ending,
attrs_list,
align: None,
shape_opt: Cached::Empty,
layout_opt: Cached::Empty,
shaping,
metadata: None,
}
}
/// Resets the current line with new internal values.
///
/// Avoids deallocating internal caches so they can be reused.
pub fn reset_new<T: Into<String>>(
&mut self,
text: T,
ending: LineEnding,
attrs_list: AttrsList,
shaping: Shaping,
) {
self.text = text.into();
self.ending = ending;
self.attrs_list = attrs_list;
self.align = None;
self.shape_opt.set_unused();
self.layout_opt.set_unused();
self.shaping = shaping;
self.metadata = None;
}
/// Get current text
pub fn text(&self) -> &str {
&self.text
}
/// Set text and attributes list
///
/// Will reset shape and layout if it differs from current text and attributes list.
/// Returns true if the line was reset
pub fn set_text<T: AsRef<str>>(
&mut self,
text: T,
ending: LineEnding,
attrs_list: AttrsList,
) -> bool {
let text = text.as_ref();
if text != self.text || ending != self.ending || attrs_list != self.attrs_list {
self.text.clear();
self.text.push_str(text);
self.ending = ending;
self.attrs_list = attrs_list;
self.reset();
true
} else {
false
}
}
/// Consume this line, returning only its text contents as a String.
pub fn into_text(self) -> String {
self.text
}
/// Get line ending
pub fn ending(&self) -> LineEnding {
self.ending
}
/// Set line ending
///
/// Will reset shape and layout if it differs from current line ending.
/// Returns true if the line was reset
pub fn set_ending(&mut self, ending: LineEnding) -> bool {
if ending != self.ending {
self.ending = ending;
self.reset_shaping();
true
} else {
false
}
}
/// Get attributes list
pub fn attrs_list(&self) -> &AttrsList {
&self.attrs_list
}
/// Set attributes list
///
/// Will reset shape and layout if it differs from current attributes list.
/// Returns true if the line was reset
pub fn set_attrs_list(&mut self, attrs_list: AttrsList) -> bool {
if attrs_list != self.attrs_list {
self.attrs_list = attrs_list;
self.reset_shaping();
true
} else {
false
}
}
/// Get the Text alignment
pub fn align(&self) -> Option<Align> {
self.align
}
/// Set the text alignment
///
/// Will reset layout if it differs from current alignment.
/// Setting to None will use `Align::Right` for RTL lines, and `Align::Left` for LTR lines.
/// Returns true if the line was reset
pub fn set_align(&mut self, align: Option<Align>) -> bool {
if align != self.align {
self.align = align;
self.reset_layout();
true
} else {
false
}
}
/// Append line at end of this line
///
/// The wrap setting of the appended line will be lost
pub fn append(&mut self, other: Self) {
let len = self.text.len();
self.text.push_str(other.text());
if other.attrs_list.defaults() != self.attrs_list.defaults() {
// If default formatting does not match, make a new span for it
self.attrs_list
.add_span(len..len + other.text().len(), other.attrs_list.defaults());
}
for (other_range, attrs) in other.attrs_list.spans_iter() {
// Add previous attrs spans
let range = other_range.start + len..other_range.end + len;
self.attrs_list.add_span(range, attrs.as_attrs());
}
self.reset();
}
/// Split off new line at index
pub fn split_off(&mut self, index: usize) -> Self {
let text = self.text.split_off(index);
let attrs_list = self.attrs_list.split_off(index);
self.reset();
let mut new = Self::new(text, self.ending, attrs_list, self.shaping);
new.align = self.align;
new
}
/// Reset shaping, layout, and metadata caches
pub fn reset(&mut self) {
self.metadata = None;
self.reset_shaping();
}
/// Reset shaping and layout caches
pub fn reset_shaping(&mut self) {
self.shape_opt.set_unused();
self.reset_layout();
}
/// Reset only layout cache
pub fn reset_layout(&mut self) {
self.layout_opt.set_unused();
}
/// Shape line, will cache results
#[allow(clippy::missing_panics_doc)]
pub fn shape(&mut self, font_system: &mut FontSystem, tab_width: u16) -> &ShapeLine {
if self.shape_opt.is_unused() {
let mut line = self
.shape_opt
.take_unused()
.unwrap_or_else(ShapeLine::empty);
line.build(
font_system,
&self.text,
&self.attrs_list,
self.shaping,
tab_width,
);
self.shape_opt.set_used(line);
self.layout_opt.set_unused();
}
self.shape_opt.get().expect("shape not found")
}
/// Get line shaping cache
pub fn shape_opt(&self) -> Option<&ShapeLine> {
self.shape_opt.get()
}
/// Layout line, will cache results
#[allow(clippy::missing_panics_doc)]
pub fn layout(
&mut self,
font_system: &mut FontSystem,
font_size: f32,
width_opt: Option<f32>,
wrap: Wrap,
match_mono_width: Option<f32>,
tab_width: u16,
) -> &[LayoutLine] {
if self.layout_opt.is_unused() {
let align = self.align;
let mut layout = self
.layout_opt
.take_unused()
.unwrap_or_else(|| Vec::with_capacity(1));
let shape = self.shape(font_system, tab_width);
shape.layout_to_buffer(
&mut font_system.shape_buffer,
font_size,
width_opt,
wrap,
align,
&mut layout,
match_mono_width,
);
self.layout_opt.set_used(layout);
}
self.layout_opt.get().expect("layout not found")
}
/// Get line layout cache
pub fn layout_opt(&self) -> Option<&Vec<LayoutLine>> {
self.layout_opt.get()
}
/// Get line metadata. This will be None if [`BufferLine::set_metadata`] has not been called
/// after the last reset of shaping and layout caches
pub fn metadata(&self) -> Option<usize> {
self.metadata
}
/// Set line metadata. This is stored until the next line reset
pub fn set_metadata(&mut self, metadata: usize) {
self.metadata = Some(metadata);
}
/// Makes an empty buffer line.
///
/// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
pub(crate) fn empty() -> Self {
Self {
text: String::default(),
ending: LineEnding::default(),
attrs_list: AttrsList::new(Attrs::new()),
align: None,
shape_opt: Cached::Empty,
layout_opt: Cached::Empty,
shaping: Shaping::Advanced,
metadata: None,
}
}
/// Reclaim attributes list memory that isn't needed any longer.
///
/// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
pub(crate) fn reclaim_attrs(&mut self) -> AttrsList {
mem::replace(&mut self.attrs_list, AttrsList::new(Attrs::new()))
}
/// Reclaim text memory that isn't needed any longer.
///
/// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
pub(crate) fn reclaim_text(&mut self) -> String {
let mut text = mem::take(&mut self.text);
text.clear();
text
}
}

81
vendor/cosmic-text/src/cached.rs vendored Normal file
View File

@@ -0,0 +1,81 @@
use core::fmt::Debug;
use core::mem;
/// Helper for caching a value when the value is optionally present in the 'unused' state.
#[derive(Clone, Debug)]
pub enum Cached<T: Clone + Debug> {
Empty,
Unused(T),
Used(T),
}
impl<T: Clone + Debug> Cached<T> {
/// Gets the value if in state `Self::Used`.
pub fn get(&self) -> Option<&T> {
match self {
Self::Empty | Self::Unused(_) => None,
Self::Used(t) => Some(t),
}
}
/// Gets the value mutably if in state `Self::Used`.
pub fn get_mut(&mut self) -> Option<&mut T> {
match self {
Self::Empty | Self::Unused(_) => None,
Self::Used(t) => Some(t),
}
}
/// Checks if the value is empty or unused.
pub fn is_unused(&self) -> bool {
match self {
Self::Empty | Self::Unused(_) => true,
Self::Used(_) => false,
}
}
/// Checks if the value is used (i.e. cached for access).
pub fn is_used(&self) -> bool {
match self {
Self::Empty | Self::Unused(_) => false,
Self::Used(_) => true,
}
}
/// Takes the buffered value if in state `Self::Unused`.
pub fn take_unused(&mut self) -> Option<T> {
if matches!(*self, Self::Unused(_)) {
let Self::Unused(val) = mem::replace(self, Self::Empty) else {
unreachable!()
};
Some(val)
} else {
None
}
}
/// Takes the cached value if in state `Self::Used`.
pub fn take_used(&mut self) -> Option<T> {
if matches!(*self, Self::Used(_)) {
let Self::Used(val) = mem::replace(self, Self::Empty) else {
unreachable!()
};
Some(val)
} else {
None
}
}
/// Moves the value from `Self::Used` to `Self::Unused`.
#[allow(clippy::missing_panics_doc)]
pub fn set_unused(&mut self) {
if matches!(*self, Self::Used(_)) {
*self = Self::Unused(self.take_used().expect("cached value should be used"));
}
}
/// Sets the value to `Self::Used`.
pub fn set_used(&mut self, val: T) {
*self = Self::Used(val);
}
}

156
vendor/cosmic-text/src/cursor.rs vendored Normal file
View File

@@ -0,0 +1,156 @@
/// Current cursor location
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct Cursor {
/// Index of [`BufferLine`] in [`Buffer::lines`]
pub line: usize,
/// First-byte-index of glyph at cursor (will insert behind this glyph)
pub index: usize,
/// Whether to associate the cursor with the run before it or the run after it if placed at the
/// boundary between two runs
pub affinity: Affinity,
}
impl Cursor {
/// Create a new cursor
pub const fn new(line: usize, index: usize) -> Self {
Self::new_with_affinity(line, index, Affinity::Before)
}
/// Create a new cursor, specifying the affinity
pub const fn new_with_affinity(line: usize, index: usize, affinity: Affinity) -> Self {
Self {
line,
index,
affinity,
}
}
}
/// Whether to associate cursors placed at a boundary between runs with the run before or after it.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub enum Affinity {
#[default]
Before,
After,
}
impl Affinity {
pub fn before(&self) -> bool {
*self == Self::Before
}
pub fn after(&self) -> bool {
*self == Self::After
}
pub fn from_before(before: bool) -> Self {
if before {
Self::Before
} else {
Self::After
}
}
pub fn from_after(after: bool) -> Self {
if after {
Self::After
} else {
Self::Before
}
}
}
/// The position of a cursor within a [`Buffer`].
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct LayoutCursor {
/// Index of [`BufferLine`] in [`Buffer::lines`]
pub line: usize,
/// Index of [`LayoutLine`] in [`BufferLine::layout`]
pub layout: usize,
/// Index of [`LayoutGlyph`] in [`LayoutLine::glyphs`]
pub glyph: usize,
}
impl LayoutCursor {
/// Create a new [`LayoutCursor`]
pub const fn new(line: usize, layout: usize, glyph: usize) -> Self {
Self {
line,
layout,
glyph,
}
}
}
/// A motion to perform on a [`Cursor`]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Motion {
/// Apply specific [`LayoutCursor`]
LayoutCursor(LayoutCursor),
/// Move cursor to previous character ([`Self::Left`] in LTR, [`Self::Right`] in RTL)
Previous,
/// Move cursor to next character ([`Self::Right`] in LTR, [`Self::Left`] in RTL)
Next,
/// Move cursor left
Left,
/// Move cursor right
Right,
/// Move cursor up
Up,
/// Move cursor down
Down,
/// Move cursor to start of line
Home,
/// Move cursor to start of line, skipping whitespace
SoftHome,
/// Move cursor to end of line
End,
/// Move cursor to start of paragraph
ParagraphStart,
/// Move cursor to end of paragraph
ParagraphEnd,
/// Move cursor up one page
PageUp,
/// Move cursor down one page
PageDown,
/// Move cursor up or down by a number of pixels
Vertical(i32),
/// Move cursor to previous word boundary
PreviousWord,
/// Move cursor to next word boundary
NextWord,
/// Move cursor to next word boundary to the left
LeftWord,
/// Move cursor to next word boundary to the right
RightWord,
/// Move cursor to the start of the document
BufferStart,
/// Move cursor to the end of the document
BufferEnd,
/// Move cursor to specific line
GotoLine(usize),
}
/// Scroll position in [`Buffer`]
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct Scroll {
/// Index of [`BufferLine`] in [`Buffer::lines`]. This will be adjusted as needed if layout is
/// out of bounds
pub line: usize,
/// Pixel offset from the start of the [`BufferLine`]. This will be adjusted as needed
/// if it is negative or exceeds the height of the [`BufferLine::layout`] lines.
pub vertical: f32,
/// The horizontal position of scroll in fractional pixels
pub horizontal: f32,
}
impl Scroll {
/// Create a new scroll
pub const fn new(line: usize, vertical: f32, horizontal: f32) -> Self {
Self {
line,
vertical,
horizontal,
}
}
}

922
vendor/cosmic-text/src/edit/editor.rs vendored Normal file
View File

@@ -0,0 +1,922 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
#[cfg(not(feature = "std"))]
use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::{cmp, iter::once};
use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")]
use crate::Color;
use crate::{
Action, Attrs, AttrsList, BorrowedWithFontSystem, BufferLine, BufferRef, Change, ChangeItem,
Cursor, Edit, FontSystem, LayoutRun, Selection, Shaping,
};
/// A wrapper of [`Buffer`] for easy editing
#[derive(Debug, Clone)]
pub struct Editor<'buffer> {
buffer_ref: BufferRef<'buffer>,
cursor: Cursor,
cursor_x_opt: Option<i32>,
selection: Selection,
cursor_moved: bool,
auto_indent: bool,
change: Option<Change>,
}
fn cursor_glyph_opt(cursor: &Cursor, run: &LayoutRun) -> Option<(usize, f32)> {
if cursor.line == run.line_i {
for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
if cursor.index == glyph.start {
return Some((glyph_i, 0.0));
} else if cursor.index > glyph.start && cursor.index < glyph.end {
// Guess x offset based on characters
let mut before = 0;
let mut total = 0;
let cluster = &run.text[glyph.start..glyph.end];
for (i, _) in cluster.grapheme_indices(true) {
if glyph.start + i < cursor.index {
before += 1;
}
total += 1;
}
let offset = glyph.w * (before as f32) / (total as f32);
return Some((glyph_i, offset));
}
}
match run.glyphs.last() {
Some(glyph) => {
if cursor.index == glyph.end {
return Some((run.glyphs.len(), 0.0));
}
}
None => {
return Some((0, 0.0));
}
}
}
None
}
fn cursor_position(cursor: &Cursor, run: &LayoutRun) -> Option<(i32, i32)> {
let (cursor_glyph, cursor_glyph_offset) = cursor_glyph_opt(cursor, run)?;
let x = match run.glyphs.get(cursor_glyph) {
Some(glyph) => {
// Start of detected glyph
if glyph.level.is_rtl() {
(glyph.x + glyph.w - cursor_glyph_offset) as i32
} else {
(glyph.x + cursor_glyph_offset) as i32
}
}
None => match run.glyphs.last() {
Some(glyph) => {
// End of last glyph
if glyph.level.is_rtl() {
glyph.x as i32
} else {
(glyph.x + glyph.w) as i32
}
}
None => {
// Start of empty line
0
}
},
};
Some((x, run.line_top as i32))
}
impl<'buffer> Editor<'buffer> {
/// Create a new [`Editor`] with the provided [`Buffer`]
pub fn new(buffer: impl Into<BufferRef<'buffer>>) -> Self {
Self {
buffer_ref: buffer.into(),
cursor: Cursor::default(),
cursor_x_opt: None,
selection: Selection::None,
cursor_moved: false,
auto_indent: false,
change: None,
}
}
/// Draw the editor
#[cfg(feature = "swash")]
#[allow(clippy::too_many_arguments)]
pub fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
text_color: Color,
cursor_color: Color,
selection_color: Color,
selected_text_color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let selection_bounds = self.selection_bounds();
self.with_buffer(|buffer| {
for run in buffer.layout_runs() {
let line_i = run.line_i;
let line_y = run.line_y;
let line_top = run.line_top;
let line_height = run.line_height;
// Highlight selection
if let Some((start, end)) = selection_bounds {
if line_i >= start.line && line_i <= end.line {
let mut range_opt = None;
for glyph in run.glyphs.iter() {
// Guess x offset based on characters
let cluster = &run.text[glyph.start..glyph.end];
let total = cluster.grapheme_indices(true).count();
let mut c_x = glyph.x;
let c_w = glyph.w / total as f32;
for (i, c) in cluster.grapheme_indices(true) {
let c_start = glyph.start + i;
let c_end = glyph.start + i + c.len();
if (start.line != line_i || c_end > start.index)
&& (end.line != line_i || c_start < end.index)
{
range_opt = match range_opt.take() {
Some((min, max)) => Some((
cmp::min(min, c_x as i32),
cmp::max(max, (c_x + c_w) as i32),
)),
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
selection_color,
);
}
c_x += c_w;
}
}
if run.glyphs.is_empty() && end.line > line_i {
// Highlight all of internal empty lines
range_opt = Some((0, buffer.size().0.unwrap_or(0.0) as i32));
}
if let Some((mut min, mut max)) = range_opt.take() {
if end.line > line_i {
// Draw to end of line
if run.rtl {
min = 0;
} else {
max = buffer.size().0.unwrap_or(0.0) as i32;
}
}
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
selection_color,
);
}
}
}
// Draw cursor
if let Some((x, y)) = cursor_position(&self.cursor, &run) {
f(x, y, 1, line_height as u32, cursor_color);
}
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let mut glyph_color = match glyph.color_opt {
Some(some) => some,
None => text_color,
};
if text_color != selected_text_color {
if let Some((start, end)) = selection_bounds {
if line_i >= start.line
&& line_i <= end.line
&& (start.line != line_i || glyph.end > start.index)
&& (end.line != line_i || glyph.start < end.index)
{
glyph_color = selected_text_color;
}
}
}
cache.with_pixels(
font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
f(
physical_glyph.x + x,
line_y as i32 + physical_glyph.y + y,
1,
1,
color,
);
},
);
}
}
});
}
}
impl<'buffer> Edit<'buffer> for Editor<'buffer> {
fn buffer_ref(&self) -> &BufferRef<'buffer> {
&self.buffer_ref
}
fn buffer_ref_mut(&mut self) -> &mut BufferRef<'buffer> {
&mut self.buffer_ref
}
fn cursor(&self) -> Cursor {
self.cursor
}
fn set_cursor(&mut self, cursor: Cursor) {
if self.cursor != cursor {
self.cursor = cursor;
self.cursor_moved = true;
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
}
fn selection(&self) -> Selection {
self.selection
}
fn set_selection(&mut self, selection: Selection) {
if self.selection != selection {
self.selection = selection;
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
}
fn auto_indent(&self) -> bool {
self.auto_indent
}
fn set_auto_indent(&mut self, auto_indent: bool) {
self.auto_indent = auto_indent;
}
fn tab_width(&self) -> u16 {
self.with_buffer(|buffer| buffer.tab_width())
}
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
self.with_buffer_mut(|buffer| buffer.set_tab_width(font_system, tab_width));
}
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
if self.cursor_moved {
let cursor = self.cursor;
self.with_buffer_mut(|buffer| buffer.shape_until_cursor(font_system, cursor, prune));
self.cursor_moved = false;
} else {
self.with_buffer_mut(|buffer| buffer.shape_until_scroll(font_system, prune));
}
}
fn delete_range(&mut self, start: Cursor, end: Cursor) {
let change_item = self.with_buffer_mut(|buffer| {
// Collect removed data for change tracking
let mut change_lines = Vec::new();
// Delete the selection from the last line
let end_line_opt = if end.line > start.line {
// Get part of line after selection
let after = buffer.lines[end.line].split_off(end.index);
// Remove end line
let removed = buffer.lines.remove(end.line);
change_lines.insert(0, removed.text().to_string());
Some(after)
} else {
None
};
// Delete interior lines (in reverse for safety)
for line_i in (start.line + 1..end.line).rev() {
let removed = buffer.lines.remove(line_i);
change_lines.insert(0, removed.text().to_string());
}
// Delete the selection from the first line
{
// Get part after selection if start line is also end line
let after_opt = if start.line == end.line {
Some(buffer.lines[start.line].split_off(end.index))
} else {
None
};
// Delete selected part of line
let removed = buffer.lines[start.line].split_off(start.index);
change_lines.insert(0, removed.text().to_string());
// Re-add part of line after selection
if let Some(after) = after_opt {
buffer.lines[start.line].append(after);
}
// Re-add valid parts of end line
if let Some(end_line) = end_line_opt {
buffer.lines[start.line].append(end_line);
}
}
ChangeItem {
start,
end,
text: change_lines.join("\n"),
insert: false,
}
});
if let Some(ref mut change) = self.change {
change.items.push(change_item);
}
}
fn insert_at(
&mut self,
mut cursor: Cursor,
data: &str,
attrs_list: Option<AttrsList>,
) -> Cursor {
let mut remaining_split_len = data.len();
if remaining_split_len == 0 {
return cursor;
}
let change_item = self.with_buffer_mut(|buffer| {
// Save cursor for change tracking
let start = cursor;
// Ensure there are enough lines in the buffer to handle this cursor
while cursor.line >= buffer.lines.len() {
let ending = buffer
.lines
.last()
.map(|line| line.ending())
.unwrap_or_default();
let line = BufferLine::new(
String::new(),
ending,
AttrsList::new(attrs_list.as_ref().map_or_else(
|| {
buffer
.lines
.last()
.map_or(Attrs::new(), |line| line.attrs_list().defaults())
},
|x| x.defaults(),
)),
Shaping::Advanced,
);
buffer.lines.push(line);
}
let line: &mut BufferLine = &mut buffer.lines[cursor.line];
let insert_line = cursor.line + 1;
let ending = line.ending();
// Collect text after insertion as a line
let after: BufferLine = line.split_off(cursor.index);
let after_len = after.text().len();
// Collect attributes
let mut final_attrs = attrs_list.unwrap_or_else(|| {
AttrsList::new(line.attrs_list().get_span(cursor.index.saturating_sub(1)))
});
// Append the inserted text, line by line
// we want to see a blank entry if the string ends with a newline
//TODO: adjust this to get line ending from data?
let addendum = once("").filter(|_| data.ends_with('\n'));
let mut lines_iter = data.split_inclusive('\n').chain(addendum);
if let Some(data_line) = lines_iter.next() {
let mut these_attrs = final_attrs.split_off(data_line.len());
remaining_split_len -= data_line.len();
core::mem::swap(&mut these_attrs, &mut final_attrs);
line.append(BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
ending,
these_attrs,
Shaping::Advanced,
));
} else {
panic!("str::lines() did not yield any elements");
}
if let Some(data_line) = lines_iter.next_back() {
remaining_split_len -= data_line.len();
let mut tmp = BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
ending,
final_attrs.split_off(remaining_split_len),
Shaping::Advanced,
);
tmp.append(after);
buffer.lines.insert(insert_line, tmp);
cursor.line += 1;
} else {
line.append(after);
}
for data_line in lines_iter.rev() {
remaining_split_len -= data_line.len();
let tmp = BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
ending,
final_attrs.split_off(remaining_split_len),
Shaping::Advanced,
);
buffer.lines.insert(insert_line, tmp);
cursor.line += 1;
}
assert_eq!(remaining_split_len, 0);
// Append the text after insertion
cursor.index = buffer.lines[cursor.line].text().len() - after_len;
ChangeItem {
start,
end: cursor,
text: data.to_string(),
insert: true,
}
});
if let Some(ref mut change) = self.change {
change.items.push(change_item);
}
cursor
}
fn copy_selection(&self) -> Option<String> {
let (start, end) = self.selection_bounds()?;
self.with_buffer(|buffer| {
let mut selection = String::new();
// Take the selection from the first line
{
// Add selected part of line to string
if start.line == end.line {
selection.push_str(&buffer.lines[start.line].text()[start.index..end.index]);
} else {
selection.push_str(&buffer.lines[start.line].text()[start.index..]);
selection.push('\n');
}
}
// Take the selection from all interior lines (if they exist)
for line_i in start.line + 1..end.line {
selection.push_str(buffer.lines[line_i].text());
selection.push('\n');
}
// Take the selection from the last line
if end.line > start.line {
// Add selected part of line to string
selection.push_str(&buffer.lines[end.line].text()[..end.index]);
}
Some(selection)
})
}
fn delete_selection(&mut self) -> bool {
let (start, end) = match self.selection_bounds() {
Some(some) => some,
None => return false,
};
// Reset cursor to start of selection
self.cursor = start;
// Reset selection to None
self.selection = Selection::None;
// Delete from start to end of selection
self.delete_range(start, end);
true
}
fn apply_change(&mut self, change: &Change) -> bool {
// Cannot apply changes if there is a pending change
if let Some(pending) = self.change.take() {
if !pending.items.is_empty() {
//TODO: is this a good idea?
log::warn!("pending change caused apply_change to be ignored!");
self.change = Some(pending);
return false;
}
}
for item in change.items.iter() {
//TODO: edit cursor if needed?
if item.insert {
self.cursor = self.insert_at(item.start, &item.text, None);
} else {
self.cursor = item.start;
self.delete_range(item.start, item.end);
}
}
true
}
fn start_change(&mut self) {
if self.change.is_none() {
self.change = Some(Change::default());
}
}
fn finish_change(&mut self) -> Option<Change> {
self.change.take()
}
fn action(&mut self, font_system: &mut FontSystem, action: Action) {
let old_cursor = self.cursor;
match action {
Action::Motion(motion) => {
let cursor = self.cursor;
let cursor_x_opt = self.cursor_x_opt;
if let Some((new_cursor, new_cursor_x_opt)) = self.with_buffer_mut(|buffer| {
buffer.cursor_motion(font_system, cursor, cursor_x_opt, motion)
}) {
self.cursor = new_cursor;
self.cursor_x_opt = new_cursor_x_opt;
}
}
Action::Escape => {
match self.selection {
Selection::None => {}
_ => self.with_buffer_mut(|buffer| buffer.set_redraw(true)),
}
self.selection = Selection::None;
}
Action::Insert(character) => {
if character.is_control() && !['\t', '\n', '\u{92}'].contains(&character) {
// Filter out special chars (except for tab), use Action instead
log::debug!("Refusing to insert control character {:?}", character);
} else if character == '\n' {
self.action(font_system, Action::Enter);
} else {
let mut str_buf = [0u8; 8];
let str_ref = character.encode_utf8(&mut str_buf);
self.insert_string(str_ref, None);
}
}
Action::Enter => {
//TODO: what about indenting more after opening brackets or parentheses?
if self.auto_indent {
let mut string = String::from("\n");
self.with_buffer(|buffer| {
let line = &buffer.lines[self.cursor.line];
let text = line.text();
for c in text.chars() {
if c.is_whitespace() {
string.push(c);
} else {
break;
}
}
});
self.insert_string(&string, None);
} else {
self.insert_string("\n", None);
}
// Ensure line is properly shaped and laid out (for potential immediate commands)
let line_i = self.cursor.line;
self.with_buffer_mut(|buffer| {
buffer.line_layout(font_system, line_i);
});
}
Action::Backspace => {
if self.delete_selection() {
// Deleted selection
} else {
// Save current cursor as end
let end = self.cursor;
if self.cursor.index > 0 {
// Move cursor to previous character index
self.cursor.index = self.with_buffer(|buffer| {
buffer.lines[self.cursor.line].text()[..self.cursor.index]
.char_indices()
.next_back()
.map_or(0, |(i, _)| i)
});
} else if self.cursor.line > 0 {
// Move cursor to previous line
self.cursor.line -= 1;
self.cursor.index =
self.with_buffer(|buffer| buffer.lines[self.cursor.line].text().len());
}
if self.cursor != end {
// Delete range
self.delete_range(self.cursor, end);
}
}
}
Action::Delete => {
if self.delete_selection() {
// Deleted selection
} else {
// Save current cursor as start and end
let mut start = self.cursor;
let mut end = self.cursor;
self.with_buffer(|buffer| {
if start.index < buffer.lines[start.line].text().len() {
let line = &buffer.lines[start.line];
let range_opt = line
.text()
.grapheme_indices(true)
.take_while(|(i, _)| *i <= start.index)
.last()
.map(|(i, c)| i..(i + c.len()));
if let Some(range) = range_opt {
start.index = range.start;
end.index = range.end;
}
} else if start.line + 1 < buffer.lines.len() {
end.line += 1;
end.index = 0;
}
});
if start != end {
self.cursor = start;
self.delete_range(start, end);
}
}
}
Action::Indent => {
// Get start and end of selection
let (start, end) = match self.selection_bounds() {
Some(some) => some,
None => (self.cursor, self.cursor),
};
// For every line in selection
let tab_width: usize = self.tab_width().into();
for line_i in start.line..=end.line {
// Determine indexes of last indent and first character after whitespace
let mut after_whitespace = 0;
let mut required_indent = 0;
self.with_buffer(|buffer| {
let line = &buffer.lines[line_i];
let text = line.text();
// Default to end of line if no non-whitespace found
after_whitespace = text.len();
for (count, (index, c)) in text.char_indices().enumerate() {
if !c.is_whitespace() {
after_whitespace = index;
required_indent = tab_width - (count % tab_width);
break;
}
}
});
// No indent required (not possible?)
if required_indent == 0 {
required_indent = tab_width;
}
self.insert_at(
Cursor::new(line_i, after_whitespace),
&" ".repeat(required_indent),
None,
);
// Adjust cursor
if self.cursor.line == line_i {
//TODO: should we be forcing cursor index to current indent location?
if self.cursor.index < after_whitespace {
self.cursor.index = after_whitespace;
}
self.cursor.index += required_indent;
}
// Adjust selection
match self.selection {
Selection::None => {}
Selection::Normal(ref mut select)
| Selection::Line(ref mut select)
| Selection::Word(ref mut select) => {
if select.line == line_i && select.index >= after_whitespace {
select.index += required_indent;
}
}
}
// Request redraw
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
}
Action::Unindent => {
// Get start and end of selection
let (start, end) = match self.selection_bounds() {
Some(some) => some,
None => (self.cursor, self.cursor),
};
// For every line in selection
let tab_width: usize = self.tab_width().into();
for line_i in start.line..=end.line {
// Determine indexes of last indent and first character after whitespace
let mut last_indent = 0;
let mut after_whitespace = 0;
self.with_buffer(|buffer| {
let line = &buffer.lines[line_i];
let text = line.text();
// Default to end of line if no non-whitespace found
after_whitespace = text.len();
for (count, (index, c)) in text.char_indices().enumerate() {
if !c.is_whitespace() {
after_whitespace = index;
break;
}
if count % tab_width == 0 {
last_indent = index;
}
}
});
// No de-indent required
if last_indent == after_whitespace {
continue;
}
// Delete one indent
self.delete_range(
Cursor::new(line_i, last_indent),
Cursor::new(line_i, after_whitespace),
);
// Adjust cursor
if self.cursor.line == line_i && self.cursor.index > last_indent {
self.cursor.index -= after_whitespace - last_indent;
}
// Adjust selection
match self.selection {
Selection::None => {}
Selection::Normal(ref mut select)
| Selection::Line(ref mut select)
| Selection::Word(ref mut select) => {
if select.line == line_i && select.index > last_indent {
select.index -= after_whitespace - last_indent;
}
}
}
// Request redraw
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
}
Action::Click { x, y } => {
self.set_selection(Selection::None);
if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32))
{
if new_cursor != self.cursor {
self.cursor = new_cursor;
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
}
}
Action::DoubleClick { x, y } => {
self.set_selection(Selection::None);
if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32))
{
if new_cursor != self.cursor {
self.cursor = new_cursor;
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
self.selection = Selection::Word(self.cursor);
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
}
Action::TripleClick { x, y } => {
self.set_selection(Selection::None);
if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32))
{
if new_cursor != self.cursor {
self.cursor = new_cursor;
}
self.selection = Selection::Line(self.cursor);
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
}
Action::Drag { x, y } => {
if self.selection == Selection::None {
self.selection = Selection::Normal(self.cursor);
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32))
{
if new_cursor != self.cursor {
self.cursor = new_cursor;
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
}
}
}
Action::Scroll { lines } => {
self.with_buffer_mut(|buffer| {
let mut scroll = buffer.scroll();
//TODO: align to layout lines
scroll.vertical += lines as f32 * buffer.metrics().line_height;
buffer.set_scroll(scroll);
});
}
}
if old_cursor != self.cursor {
self.cursor_moved = true;
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
/*TODO
if let Some(glyph) = run.glyphs.get(new_cursor_glyph) {
let font_opt = self.buffer.font_system().get_font(glyph.cache_key.font_id);
let text_glyph = &run.text[glyph.start..glyph.end];
log::debug!(
"{}, {}: '{}' ('{}'): '{}' ({:?})",
self.cursor.line,
self.cursor.index,
font_opt.as_ref().map_or("?", |font| font.info.family.as_str()),
font_opt.as_ref().map_or("?", |font| font.info.post_script_name.as_str()),
text_glyph,
text_glyph
);
}
*/
}
}
fn cursor_position(&self) -> Option<(i32, i32)> {
self.with_buffer(|buffer| {
buffer
.layout_runs()
.find_map(|run| cursor_position(&self.cursor, &run))
})
}
}
impl BorrowedWithFontSystem<'_, Editor<'_>> {
#[cfg(feature = "swash")]
pub fn draw<F>(
&mut self,
cache: &mut crate::SwashCache,
text_color: Color,
cursor_color: Color,
selection_color: Color,
selected_text_color: Color,
f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
self.inner.draw(
self.font_system,
cache,
text_color,
cursor_color,
selection_color,
selected_text_color,
f,
);
}
}

366
vendor/cosmic-text/src/edit/mod.rs vendored Normal file
View File

@@ -0,0 +1,366 @@
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::cmp;
use unicode_segmentation::UnicodeSegmentation;
use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem, Motion};
pub use self::editor::*;
mod editor;
#[cfg(feature = "syntect")]
pub use self::syntect::*;
#[cfg(feature = "syntect")]
mod syntect;
#[cfg(feature = "vi")]
pub use self::vi::*;
#[cfg(feature = "vi")]
mod vi;
/// An action to perform on an [`Editor`]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Action {
/// Move the cursor with some motion
Motion(Motion),
/// Escape, clears selection
Escape,
/// Insert character at cursor
Insert(char),
/// Create new line
Enter,
/// Delete text behind cursor
Backspace,
/// Delete text in front of cursor
Delete,
// Indent text (typically Tab)
Indent,
// Unindent text (typically Shift+Tab)
Unindent,
/// Mouse click at specified position
Click {
x: i32,
y: i32,
},
/// Mouse double click at specified position
DoubleClick {
x: i32,
y: i32,
},
/// Mouse triple click at specified position
TripleClick {
x: i32,
y: i32,
},
/// Mouse drag to specified position
Drag {
x: i32,
y: i32,
},
/// Scroll specified number of lines
Scroll {
lines: i32,
},
}
#[derive(Debug)]
pub enum BufferRef<'buffer> {
Owned(Buffer),
Borrowed(&'buffer mut Buffer),
Arc(Arc<Buffer>),
}
impl Clone for BufferRef<'_> {
fn clone(&self) -> Self {
match self {
Self::Owned(buffer) => Self::Owned(buffer.clone()),
Self::Borrowed(buffer) => Self::Owned((*buffer).clone()),
Self::Arc(buffer) => Self::Arc(buffer.clone()),
}
}
}
impl From<Buffer> for BufferRef<'_> {
fn from(buffer: Buffer) -> Self {
Self::Owned(buffer)
}
}
impl<'buffer> From<&'buffer mut Buffer> for BufferRef<'buffer> {
fn from(buffer: &'buffer mut Buffer) -> Self {
Self::Borrowed(buffer)
}
}
impl From<Arc<Buffer>> for BufferRef<'_> {
fn from(arc: Arc<Buffer>) -> Self {
Self::Arc(arc)
}
}
/// A unique change to an editor
#[derive(Clone, Debug)]
pub struct ChangeItem {
/// Cursor indicating start of change
pub start: Cursor,
/// Cursor indicating end of change
pub end: Cursor,
/// Text to be inserted or deleted
pub text: String,
/// Insert if true, delete if false
pub insert: bool,
}
impl ChangeItem {
// Reverse change item (in place)
pub fn reverse(&mut self) {
self.insert = !self.insert;
}
}
/// A set of change items grouped into one logical change
#[derive(Clone, Debug, Default)]
pub struct Change {
/// Change items grouped into one change
pub items: Vec<ChangeItem>,
}
impl Change {
// Reverse change (in place)
pub fn reverse(&mut self) {
self.items.reverse();
for item in self.items.iter_mut() {
item.reverse();
}
}
}
/// Selection mode
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Selection {
/// No selection
None,
/// Normal selection
Normal(Cursor),
/// Select by lines
Line(Cursor),
/// Select by words
Word(Cursor),
//TODO: Select block
}
/// A trait to allow easy replacements of [`Editor`], like `SyntaxEditor`
pub trait Edit<'buffer> {
/// Mutably borrows `self` together with an [`FontSystem`] for more convenient methods
fn borrow_with<'font_system>(
&'font_system mut self,
font_system: &'font_system mut FontSystem,
) -> BorrowedWithFontSystem<'font_system, Self>
where
Self: Sized,
{
BorrowedWithFontSystem {
inner: self,
font_system,
}
}
/// Get the internal [`BufferRef`]
fn buffer_ref(&self) -> &BufferRef<'buffer>;
/// Get the internal [`BufferRef`]
fn buffer_ref_mut(&mut self) -> &mut BufferRef<'buffer>;
/// Get the internal [`Buffer`]
fn with_buffer<F: FnOnce(&Buffer) -> T, T>(&self, f: F) -> T {
match self.buffer_ref() {
BufferRef::Owned(buffer) => f(buffer),
BufferRef::Borrowed(buffer) => f(buffer),
BufferRef::Arc(buffer) => f(buffer),
}
}
/// Get the internal [`Buffer`], mutably
fn with_buffer_mut<F: FnOnce(&mut Buffer) -> T, T>(&mut self, f: F) -> T {
match self.buffer_ref_mut() {
BufferRef::Owned(buffer) => f(buffer),
BufferRef::Borrowed(buffer) => f(buffer),
BufferRef::Arc(buffer) => f(Arc::make_mut(buffer)),
}
}
/// Get the [`Buffer`] redraw flag
fn redraw(&self) -> bool {
self.with_buffer(|buffer| buffer.redraw())
}
/// Set the [`Buffer`] redraw flag
fn set_redraw(&mut self, redraw: bool) {
self.with_buffer_mut(|buffer| buffer.set_redraw(redraw));
}
/// Get the current cursor
fn cursor(&self) -> Cursor;
/// Set the current cursor
fn set_cursor(&mut self, cursor: Cursor);
/// Get the current selection position
fn selection(&self) -> Selection;
/// Set the current selection position
fn set_selection(&mut self, selection: Selection);
/// Get the bounds of the current selection
//TODO: will not work with Block select
fn selection_bounds(&self) -> Option<(Cursor, Cursor)> {
self.with_buffer(|buffer| {
let cursor = self.cursor();
match self.selection() {
Selection::None => None,
Selection::Normal(select) => match select.line.cmp(&cursor.line) {
cmp::Ordering::Greater => Some((cursor, select)),
cmp::Ordering::Less => Some((select, cursor)),
cmp::Ordering::Equal => {
/* select.line == cursor.line */
if select.index < cursor.index {
Some((select, cursor))
} else {
/* select.index >= cursor.index */
Some((cursor, select))
}
}
},
Selection::Line(select) => {
let start_line = cmp::min(select.line, cursor.line);
let end_line = cmp::max(select.line, cursor.line);
let end_index = buffer.lines[end_line].text().len();
Some((Cursor::new(start_line, 0), Cursor::new(end_line, end_index)))
}
Selection::Word(select) => {
let (mut start, mut end) = match select.line.cmp(&cursor.line) {
cmp::Ordering::Greater => (cursor, select),
cmp::Ordering::Less => (select, cursor),
cmp::Ordering::Equal => {
/* select.line == cursor.line */
if select.index < cursor.index {
(select, cursor)
} else {
/* select.index >= cursor.index */
(cursor, select)
}
}
};
// Move start to beginning of word
{
let line = &buffer.lines[start.line];
start.index = line
.text()
.unicode_word_indices()
.rev()
.map(|(i, _)| i)
.find(|&i| i < start.index)
.unwrap_or(0);
}
// Move end to end of word
{
let line = &buffer.lines[end.line];
end.index = line
.text()
.unicode_word_indices()
.map(|(i, word)| i + word.len())
.find(|&i| i > end.index)
.unwrap_or(line.text().len());
}
Some((start, end))
}
}
})
}
/// Get the current automatic indentation setting
fn auto_indent(&self) -> bool;
/// Enable or disable automatic indentation
fn set_auto_indent(&mut self, auto_indent: bool);
/// Get the current tab width
fn tab_width(&self) -> u16;
/// Set the current tab width. A `tab_width` of 0 is not allowed, and will be ignored
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16);
/// Shape lines until scroll, after adjusting scroll if the cursor moved
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool);
/// Delete text starting at start Cursor and ending at end Cursor
fn delete_range(&mut self, start: Cursor, end: Cursor);
/// Insert text at specified cursor with specified `attrs_list`
fn insert_at(&mut self, cursor: Cursor, data: &str, attrs_list: Option<AttrsList>) -> Cursor;
/// Copy selection
fn copy_selection(&self) -> Option<String>;
/// Delete selection, adjusting cursor and returning true if there was a selection
// Also used by backspace, delete, insert, and enter when there is a selection
fn delete_selection(&mut self) -> bool;
/// Insert a string at the current cursor or replacing the current selection with the given
/// attributes, or with the previous character's attributes if None is given.
fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>) {
self.delete_selection();
let new_cursor = self.insert_at(self.cursor(), data, attrs_list);
self.set_cursor(new_cursor);
}
/// Apply a change
fn apply_change(&mut self, change: &Change) -> bool;
/// Start collecting change
fn start_change(&mut self);
/// Get completed change
fn finish_change(&mut self) -> Option<Change>;
/// Perform an [Action] on the editor
fn action(&mut self, font_system: &mut FontSystem, action: Action);
/// Get X and Y position of the top left corner of the cursor
fn cursor_position(&self) -> Option<(i32, i32)>;
}
impl<'buffer, E: Edit<'buffer>> BorrowedWithFontSystem<'_, E> {
/// Get the internal [`Buffer`], mutably
pub fn with_buffer_mut<F: FnOnce(&mut BorrowedWithFontSystem<Buffer>) -> T, T>(
&mut self,
f: F,
) -> T {
self.inner.with_buffer_mut(|buffer| {
let mut borrowed = BorrowedWithFontSystem {
inner: buffer,
font_system: self.font_system,
};
f(&mut borrowed)
})
}
/// Set the current tab width. A `tab_width` of 0 is not allowed, and will be ignored
pub fn set_tab_width(&mut self, tab_width: u16) {
self.inner.set_tab_width(self.font_system, tab_width);
}
/// Shape lines until scroll, after adjusting scroll if the cursor moved
pub fn shape_as_needed(&mut self, prune: bool) {
self.inner.shape_as_needed(self.font_system, prune);
}
/// Perform an [Action] on the editor
pub fn action(&mut self, action: Action) {
self.inner.action(self.font_system, action);
}
}

461
vendor/cosmic-text/src/edit/syntect.rs vendored Normal file
View File

@@ -0,0 +1,461 @@
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
#[cfg(feature = "std")]
use std::{fs, io, path::Path};
use syntect::highlighting::{
FontStyle, HighlightState, Highlighter, RangedHighlightIterator, ThemeSet,
};
use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
use crate::{
Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, Editor,
FontSystem, Selection, Shaping, Style, Weight,
};
pub use syntect::highlighting::Theme as SyntaxTheme;
#[derive(Debug)]
pub struct SyntaxSystem {
pub syntax_set: SyntaxSet,
pub theme_set: ThemeSet,
}
impl SyntaxSystem {
/// Create a new [`SyntaxSystem`]
pub fn new() -> Self {
Self {
//TODO: store newlines in buffer
syntax_set: SyntaxSet::load_defaults_nonewlines(),
theme_set: ThemeSet::load_defaults(),
}
}
}
/// A wrapper of [`Editor`] with syntax highlighting provided by [`SyntaxSystem`]
#[derive(Debug)]
pub struct SyntaxEditor<'syntax_system, 'buffer> {
editor: Editor<'buffer>,
syntax_system: &'syntax_system SyntaxSystem,
syntax: &'syntax_system SyntaxReference,
theme: &'syntax_system SyntaxTheme,
highlighter: Highlighter<'syntax_system>,
syntax_cache: Vec<(ParseState, ScopeStack)>,
}
impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
/// Create a new [`SyntaxEditor`] with the provided [`Buffer`], [`SyntaxSystem`], and theme name.
///
/// A good default theme name is "base16-eighties.dark".
///
/// Returns None if theme not found
pub fn new(
buffer: impl Into<BufferRef<'buffer>>,
syntax_system: &'syntax_system SyntaxSystem,
theme_name: &str,
) -> Option<Self> {
let editor = Editor::new(buffer);
let syntax = syntax_system.syntax_set.find_syntax_plain_text();
let theme = syntax_system.theme_set.themes.get(theme_name)?;
let highlighter = Highlighter::new(theme);
Some(Self {
editor,
syntax_system,
syntax,
theme,
highlighter,
syntax_cache: Vec::new(),
})
}
/// Modifies the theme of the [`SyntaxEditor`], returning false if the theme is missing
pub fn update_theme(&mut self, theme_name: &str) -> bool {
if let Some(theme) = self.syntax_system.theme_set.themes.get(theme_name) {
if self.theme != theme {
self.theme = theme;
self.highlighter = Highlighter::new(theme);
self.syntax_cache.clear();
// Reset attrs to match default foreground and no highlighting
self.with_buffer_mut(|buffer| {
for line in buffer.lines.iter_mut() {
let mut attrs = line.attrs_list().defaults();
if let Some(foreground) = self.theme.settings.foreground {
attrs = attrs.color(Color::rgba(
foreground.r,
foreground.g,
foreground.b,
foreground.a,
));
}
line.set_attrs_list(AttrsList::new(attrs));
}
});
}
true
} else {
false
}
}
/// Load text from a file, and also set syntax to the best option
///
/// ## Errors
///
/// Returns an [`io::Error`] if reading the file fails
#[cfg(feature = "std")]
pub fn load_text<P: AsRef<Path>>(
&mut self,
font_system: &mut FontSystem,
path: P,
mut attrs: crate::Attrs,
) -> io::Result<()> {
let path = path.as_ref();
// Set attrs to match default foreground
if let Some(foreground) = self.theme.settings.foreground {
attrs = attrs.color(Color::rgba(
foreground.r,
foreground.g,
foreground.b,
foreground.a,
));
}
let text = fs::read_to_string(path)?;
self.editor.with_buffer_mut(|buffer| {
buffer.set_text(font_system, &text, attrs, Shaping::Advanced);
});
//TODO: re-use text
self.syntax = match self.syntax_system.syntax_set.find_syntax_for_file(path) {
Ok(Some(some)) => some,
Ok(None) => {
log::warn!("no syntax found for {:?}", path);
self.syntax_system.syntax_set.find_syntax_plain_text()
}
Err(err) => {
log::warn!("failed to determine syntax for {:?}: {:?}", path, err);
self.syntax_system.syntax_set.find_syntax_plain_text()
}
};
// Clear syntax cache
self.syntax_cache.clear();
Ok(())
}
/// Set syntax highlighting by file extension
pub fn syntax_by_extension(&mut self, extension: &str) {
self.syntax = match self
.syntax_system
.syntax_set
.find_syntax_by_extension(extension)
{
Some(some) => some,
None => {
log::warn!("no syntax found for {}", extension);
self.syntax_system.syntax_set.find_syntax_plain_text()
}
};
self.syntax_cache.clear();
}
/// Get the default background color
pub fn background_color(&self) -> Color {
if let Some(background) = self.theme.settings.background {
Color::rgba(background.r, background.g, background.b, background.a)
} else {
Color::rgb(0, 0, 0)
}
}
/// Get the default foreground (text) color
pub fn foreground_color(&self) -> Color {
if let Some(foreground) = self.theme.settings.foreground {
Color::rgba(foreground.r, foreground.g, foreground.b, foreground.a)
} else {
Color::rgb(0xFF, 0xFF, 0xFF)
}
}
/// Get the default cursor color
pub fn cursor_color(&self) -> Color {
if let Some(some) = self.theme.settings.caret {
Color::rgba(some.r, some.g, some.b, some.a)
} else {
self.foreground_color()
}
}
/// Get the default selection color
pub fn selection_color(&self) -> Color {
if let Some(some) = self.theme.settings.selection {
Color::rgba(some.r, some.g, some.b, some.a)
} else {
let foreground_color = self.foreground_color();
Color::rgba(
foreground_color.r(),
foreground_color.g(),
foreground_color.b(),
0x33,
)
}
}
/// Get the current syntect theme
pub fn theme(&self) -> &SyntaxTheme {
self.theme
}
/// Draw the editor
#[cfg(feature = "swash")]
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
let size = self.with_buffer(|buffer| buffer.size());
if let Some(width) = size.0 {
if let Some(height) = size.1 {
f(0, 0, width as u32, height as u32, self.background_color());
}
}
self.editor.draw(
font_system,
cache,
self.foreground_color(),
self.cursor_color(),
self.selection_color(),
self.foreground_color(),
f,
);
}
}
impl<'buffer> Edit<'buffer> for SyntaxEditor<'_, 'buffer> {
fn buffer_ref(&self) -> &BufferRef<'buffer> {
self.editor.buffer_ref()
}
fn buffer_ref_mut(&mut self) -> &mut BufferRef<'buffer> {
self.editor.buffer_ref_mut()
}
fn cursor(&self) -> Cursor {
self.editor.cursor()
}
fn set_cursor(&mut self, cursor: Cursor) {
self.editor.set_cursor(cursor);
}
fn selection(&self) -> Selection {
self.editor.selection()
}
fn set_selection(&mut self, selection: Selection) {
self.editor.set_selection(selection);
}
fn auto_indent(&self) -> bool {
self.editor.auto_indent()
}
fn set_auto_indent(&mut self, auto_indent: bool) {
self.editor.set_auto_indent(auto_indent);
}
fn tab_width(&self) -> u16 {
self.editor.tab_width()
}
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
self.editor.set_tab_width(font_system, tab_width);
}
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
#[cfg(feature = "std")]
let now = std::time::Instant::now();
let cursor = self.cursor();
self.editor.with_buffer_mut(|buffer| {
let metrics = buffer.metrics();
let scroll = buffer.scroll();
let scroll_end = scroll.vertical + buffer.size().1.unwrap_or(f32::INFINITY);
let mut total_height = 0.0;
let mut highlighted = 0;
for line_i in 0..buffer.lines.len() {
// Break out if we have reached the end of scroll and are past the cursor
if total_height > scroll_end && line_i > cursor.line {
break;
}
let line = &mut buffer.lines[line_i];
if line.metadata().is_some() && line_i < self.syntax_cache.len() {
//TODO: duplicated code!
if line_i >= scroll.line && total_height < scroll_end {
// Perform shaping and layout of this line in order to count if we have reached scroll
match buffer.line_layout(font_system, line_i) {
Some(layout_lines) => {
for layout_line in layout_lines.iter() {
total_height +=
layout_line.line_height_opt.unwrap_or(metrics.line_height);
}
}
None => {
//TODO: should this be possible?
}
}
}
continue;
}
highlighted += 1;
let (mut parse_state, scope_stack) =
if line_i > 0 && line_i <= self.syntax_cache.len() {
self.syntax_cache[line_i - 1].clone()
} else {
(ParseState::new(self.syntax), ScopeStack::new())
};
let mut highlight_state = HighlightState::new(&self.highlighter, scope_stack);
let ops = parse_state
.parse_line(line.text(), &self.syntax_system.syntax_set)
.expect("failed to parse syntax");
let ranges = RangedHighlightIterator::new(
&mut highlight_state,
&ops,
line.text(),
&self.highlighter,
);
let attrs = line.attrs_list().defaults();
let mut attrs_list = AttrsList::new(attrs);
for (style, _, range) in ranges {
let span_attrs = attrs
.color(Color::rgba(
style.foreground.r,
style.foreground.g,
style.foreground.b,
style.foreground.a,
))
//TODO: background
.style(if style.font_style.contains(FontStyle::ITALIC) {
Style::Italic
} else {
Style::Normal
})
.weight(if style.font_style.contains(FontStyle::BOLD) {
Weight::BOLD
} else {
Weight::NORMAL
}); //TODO: underline
if span_attrs != attrs {
attrs_list.add_span(range, span_attrs);
}
}
// Update line attributes. This operation only resets if the line changes
line.set_attrs_list(attrs_list);
// Perform shaping and layout of this line in order to count if we have reached scroll
if line_i >= scroll.line && total_height < scroll_end {
match buffer.line_layout(font_system, line_i) {
Some(layout_lines) => {
for layout_line in layout_lines.iter() {
total_height +=
layout_line.line_height_opt.unwrap_or(metrics.line_height);
}
}
None => {
//TODO: should this be possible?
}
}
}
let cache_item = (parse_state.clone(), highlight_state.path.clone());
if line_i < self.syntax_cache.len() {
if self.syntax_cache[line_i] != cache_item {
self.syntax_cache[line_i] = cache_item;
if line_i + 1 < buffer.lines.len() {
buffer.lines[line_i + 1].reset();
}
}
} else {
buffer.lines[line_i].set_metadata(self.syntax_cache.len());
self.syntax_cache.push(cache_item);
}
}
if highlighted > 0 {
buffer.set_redraw(true);
#[cfg(feature = "std")]
log::debug!(
"Syntax highlighted {} lines in {:?}",
highlighted,
now.elapsed()
);
}
});
self.editor.shape_as_needed(font_system, prune);
}
fn delete_range(&mut self, start: Cursor, end: Cursor) {
self.editor.delete_range(start, end);
}
fn insert_at(&mut self, cursor: Cursor, data: &str, attrs_list: Option<AttrsList>) -> Cursor {
self.editor.insert_at(cursor, data, attrs_list)
}
fn copy_selection(&self) -> Option<String> {
self.editor.copy_selection()
}
fn delete_selection(&mut self) -> bool {
self.editor.delete_selection()
}
fn apply_change(&mut self, change: &Change) -> bool {
self.editor.apply_change(change)
}
fn start_change(&mut self) {
self.editor.start_change();
}
fn finish_change(&mut self) -> Option<Change> {
self.editor.finish_change()
}
fn action(&mut self, font_system: &mut FontSystem, action: Action) {
self.editor.action(font_system, action);
}
fn cursor_position(&self) -> Option<(i32, i32)> {
self.editor.cursor_position()
}
}
impl BorrowedWithFontSystem<'_, SyntaxEditor<'_, '_>> {
/// Load text from a file, and also set syntax to the best option
///
/// ## Errors
///
/// Returns an [`io::Error`] if reading the file fails
#[cfg(feature = "std")]
pub fn load_text<P: AsRef<Path>>(&mut self, path: P, attrs: crate::Attrs) -> io::Result<()> {
self.inner.load_text(self.font_system, path, attrs)
}
#[cfg(feature = "swash")]
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
self.inner.draw(self.font_system, cache, f);
}
}

1173
vendor/cosmic-text/src/edit/vi.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use unicode_script::Script;
// Fallbacks to use after any script specific fallbacks
pub fn common_fallback() -> &'static [&'static str] {
&[
".SF NS",
"Menlo",
"Apple Color Emoji",
"Geneva",
"Arial Unicode MS",
]
}
// Fallbacks to never use
pub fn forbidden_fallback() -> &'static [&'static str] {
&[".LastResort"]
}
fn han_unification(locale: &str) -> &'static [&'static str] {
match locale {
// Japan
"ja" => &["Hiragino Sans"],
// Korea
"ko" => &["Apple SD Gothic Neo"],
// Hong Kong
"zh-HK" => &["PingFang HK"],
// Taiwan
"zh-TW" => &["PingFang TC"],
// Simplified Chinese is the default (also catches "zh-CN" for China)
_ => &["PingFang SC"],
}
}
// Fallbacks to use per script
pub fn script_fallback(script: Script, locale: &str) -> &'static [&'static str] {
//TODO: abstract style (sans/serif/monospaced)
//TODO: pull more data from about:config font.name-list.sans-serif in Firefox
match script {
Script::Adlam => &["Noto Sans Adlam"],
Script::Arabic => &["Geeza Pro"],
Script::Armenian => &["Noto Sans Armenian"],
Script::Bengali => &["Bangla Sangam MN"],
Script::Buhid => &["Noto Sans Buhid"],
Script::Canadian_Aboriginal => &["Euphemia UCAS"],
Script::Chakma => &["Noto Sans Chakma"],
Script::Devanagari => &["Devanagari Sangam MN"],
Script::Ethiopic => &["Kefa"],
Script::Gothic => &["Noto Sans Gothic"],
Script::Grantha => &["Grantha Sangam MN"],
Script::Gujarati => &["Gujarati Sangam MN"],
Script::Gurmukhi => &["Gurmukhi Sangam MN"],
Script::Han => han_unification(locale),
Script::Hangul => han_unification("ko"),
Script::Hanunoo => &["Noto Sans Hanunoo"],
Script::Hebrew => &["Arial"],
Script::Hiragana => han_unification("ja"),
Script::Javanese => &["Noto Sans Javanese"],
Script::Kannada => &["Noto Sans Kannada"],
Script::Katakana => han_unification("ja"),
Script::Khmer => &["Khmer Sangam MN"],
Script::Lao => &["Lao Sangam MN"],
Script::Malayalam => &["Malayalam Sangam MN"],
Script::Mongolian => &["Noto Sans Mongolian"],
Script::Myanmar => &["Noto Sans Myanmar"],
Script::Oriya => &["Noto Sans Oriya"],
Script::Sinhala => &["Sinhala Sangam MN"],
Script::Syriac => &["Noto Sans Syriac"],
Script::Tagalog => &["Noto Sans Tagalog"],
Script::Tagbanwa => &["Noto Sans Tagbanwa"],
Script::Tai_Le => &["Noto Sans Tai Le"],
Script::Tai_Tham => &["Noto Sans Tai Tham"],
Script::Tai_Viet => &["Noto Sans Tai Viet"],
Script::Tamil => &["InaiMathi"],
Script::Telugu => &["Telugu Sangam MN"],
Script::Thaana => &["Noto Sans Thaana"],
Script::Thai => &["Ayuthaya"],
Script::Tibetan => &["Kailasa"],
Script::Tifinagh => &["Noto Sans Tifinagh"],
Script::Vai => &["Noto Sans Vai"],
//TODO: Use han_unification?
Script::Yi => &["Noto Sans Yi", "PingFang SC"],
_ => &[],
}
}

View File

@@ -0,0 +1,321 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use alloc::sync::Arc;
use alloc::vec::Vec;
use fontdb::Family;
use unicode_script::Script;
use crate::{Font, FontMatchKey, FontSystem, ShapeBuffer};
use self::platform::*;
#[cfg(not(any(all(unix, not(target_os = "android")), target_os = "windows")))]
#[path = "other.rs"]
mod platform;
#[cfg(target_os = "macos")]
#[path = "macos.rs"]
mod platform;
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
#[path = "unix.rs"]
mod platform;
#[cfg(target_os = "windows")]
#[path = "windows.rs"]
mod platform;
#[cfg(not(feature = "warn_on_missing_glyphs"))]
use log::debug as missing_warn;
#[cfg(feature = "warn_on_missing_glyphs")]
use log::warn as missing_warn;
// Match on lowest font_weight_diff, then script_non_matches, then font_weight
// Default font gets None for both `weight_offset` and `script_non_matches`, and thus, it is
// always the first to be popped from the set.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct MonospaceFallbackInfo {
font_weight_diff: Option<u16>,
codepoint_non_matches: Option<usize>,
font_weight: u16,
id: fontdb::ID,
}
pub struct FontFallbackIter<'a> {
font_system: &'a mut FontSystem,
font_match_keys: &'a [FontMatchKey],
default_families: &'a [&'a Family<'a>],
default_i: usize,
scripts: &'a [Script],
word: &'a str,
script_i: (usize, usize),
common_i: usize,
other_i: usize,
end: bool,
}
impl<'a> FontFallbackIter<'a> {
pub fn new(
font_system: &'a mut FontSystem,
font_match_keys: &'a [FontMatchKey],
default_families: &'a [&'a Family<'a>],
scripts: &'a [Script],
word: &'a str,
) -> Self {
font_system.monospace_fallbacks_buffer.clear();
Self {
font_system,
font_match_keys,
default_families,
default_i: 0,
scripts,
word,
script_i: (0, 0),
common_i: 0,
other_i: 0,
end: false,
}
}
pub fn check_missing(&mut self, word: &str) {
if self.end {
missing_warn!(
"Failed to find any fallback for {:?} locale '{}': '{}'",
self.scripts,
self.font_system.locale(),
word
);
} else if self.other_i > 0 {
missing_warn!(
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts,
self.font_system.locale(),
self.face_name(self.font_match_keys[self.other_i - 1].id),
word
);
} else if !self.scripts.is_empty() && self.common_i > 0 {
let family = common_fallback()[self.common_i - 1];
missing_warn!(
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts,
self.font_system.locale(),
family,
word
);
}
}
pub fn face_name(&self, id: fontdb::ID) -> &str {
if let Some(face) = self.font_system.db().face(id) {
if let Some((name, _)) = face.families.first() {
name
} else {
&face.post_script_name
}
} else {
"invalid font id"
}
}
pub fn shape_caches(&mut self) -> &mut ShapeBuffer {
&mut self.font_system.shape_buffer
}
fn face_contains_family(&self, id: fontdb::ID, family_name: &str) -> bool {
if let Some(face) = self.font_system.db().face(id) {
face.families.iter().any(|(name, _)| name == family_name)
} else {
false
}
}
fn default_font_match_key(&self) -> Option<&FontMatchKey> {
let default_family = self.default_families[self.default_i - 1];
let default_family_name = self.font_system.db().family_name(default_family);
self.font_match_keys
.iter()
.filter(|m_key| m_key.font_weight_diff == 0)
.find(|m_key| self.face_contains_family(m_key.id, default_family_name))
}
}
impl Iterator for FontFallbackIter<'_> {
type Item = Arc<Font>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(fallback_info) = self.font_system.monospace_fallbacks_buffer.pop_first() {
if let Some(font) = self.font_system.get_font(fallback_info.id) {
return Some(font);
}
}
let font_match_keys_iter = |is_mono| {
self.font_match_keys
.iter()
.filter(move |m_key| m_key.font_weight_diff == 0 || is_mono)
};
'DEF_FAM: while self.default_i < self.default_families.len() {
self.default_i += 1;
let is_mono = self.default_families[self.default_i - 1] == &Family::Monospace;
let default_font_match_key = self.default_font_match_key().cloned();
let word_chars_count = self.word.chars().count();
macro_rules! mk_mono_fallback_info {
($m_key:expr) => {{
let supported_cp_count_opt = self
.font_system
.get_font_supported_codepoints_in_word($m_key.id, self.word);
supported_cp_count_opt.map(|supported_cp_count| {
let codepoint_non_matches = word_chars_count - supported_cp_count;
MonospaceFallbackInfo {
font_weight_diff: Some($m_key.font_weight_diff),
codepoint_non_matches: Some(codepoint_non_matches),
font_weight: $m_key.font_weight,
id: $m_key.id,
}
})
}};
}
match (is_mono, default_font_match_key.as_ref()) {
(false, None) => break 'DEF_FAM,
(false, Some(m_key)) => {
if let Some(font) = self.font_system.get_font(m_key.id) {
return Some(font);
} else {
break 'DEF_FAM;
}
}
(true, None) => (),
(true, Some(m_key)) => {
// Default Monospace font
if let Some(mut fallback_info) = mk_mono_fallback_info!(m_key) {
fallback_info.font_weight_diff = None;
// Return early if default Monospace font supports all word codepoints.
// Otherewise, add to fallbacks set
if fallback_info.codepoint_non_matches == Some(0) {
if let Some(font) = self.font_system.get_font(m_key.id) {
return Some(font);
}
} else {
assert!(self
.font_system
.monospace_fallbacks_buffer
.insert(fallback_info));
}
}
}
};
let mono_ids_for_scripts = if is_mono && !self.scripts.is_empty() {
let scripts = self.scripts.iter().filter_map(|script| {
let script_as_lower = script.short_name().to_lowercase();
<[u8; 4]>::try_from(script_as_lower.as_bytes()).ok()
});
self.font_system.get_monospace_ids_for_scripts(scripts)
} else {
Vec::new()
};
for m_key in font_match_keys_iter(is_mono) {
if Some(m_key.id) != default_font_match_key.as_ref().map(|m_key| m_key.id) {
let is_mono_id = if mono_ids_for_scripts.is_empty() {
self.font_system.is_monospace(m_key.id)
} else {
mono_ids_for_scripts.binary_search(&m_key.id).is_ok()
};
if is_mono_id {
let supported_cp_count_opt = self
.font_system
.get_font_supported_codepoints_in_word(m_key.id, self.word);
if let Some(supported_cp_count) = supported_cp_count_opt {
let codepoint_non_matches =
self.word.chars().count() - supported_cp_count;
let fallback_info = MonospaceFallbackInfo {
font_weight_diff: Some(m_key.font_weight_diff),
codepoint_non_matches: Some(codepoint_non_matches),
font_weight: m_key.font_weight,
id: m_key.id,
};
assert!(self
.font_system
.monospace_fallbacks_buffer
.insert(fallback_info));
}
}
}
}
// If default family is Monospace fallback to first monospaced font
if let Some(fallback_info) = self.font_system.monospace_fallbacks_buffer.pop_first() {
if let Some(font) = self.font_system.get_font(fallback_info.id) {
return Some(font);
}
}
}
while self.script_i.0 < self.scripts.len() {
let script = self.scripts[self.script_i.0];
let script_families = script_fallback(script, self.font_system.locale());
while self.script_i.1 < script_families.len() {
let script_family = script_families[self.script_i.1];
self.script_i.1 += 1;
for m_key in font_match_keys_iter(false) {
if self.face_contains_family(m_key.id, script_family) {
if let Some(font) = self.font_system.get_font(m_key.id) {
return Some(font);
}
}
}
log::debug!(
"failed to find family '{}' for script {:?} and locale '{}'",
script_family,
script,
self.font_system.locale(),
);
}
self.script_i.0 += 1;
self.script_i.1 = 0;
}
let common_families = common_fallback();
while self.common_i < common_families.len() {
let common_family = common_families[self.common_i];
self.common_i += 1;
for m_key in font_match_keys_iter(false) {
if self.face_contains_family(m_key.id, common_family) {
if let Some(font) = self.font_system.get_font(m_key.id) {
return Some(font);
}
}
}
log::debug!("failed to find family '{}'", common_family);
}
//TODO: do we need to do this?
//TODO: do not evaluate fonts more than once!
let forbidden_families = forbidden_fallback();
while self.other_i < self.font_match_keys.len() {
let id = self.font_match_keys[self.other_i].id;
self.other_i += 1;
if forbidden_families
.iter()
.all(|family_name| !self.face_contains_family(id, family_name))
{
if let Some(font) = self.font_system.get_font(id) {
return Some(font);
}
}
}
self.end = true;
None
}
}

View File

@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use unicode_script::Script;
// Fallbacks to use after any script specific fallbacks
pub fn common_fallback() -> &'static [&'static str] {
&[]
}
// Fallbacks to never use
pub fn forbidden_fallback() -> &'static [&'static str] {
&[]
}
// Fallbacks to use per script
pub fn script_fallback(_script: Script, _locale: &str) -> &'static [&'static str] {
&[]
}

View File

@@ -0,0 +1,105 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use unicode_script::Script;
// Fallbacks to use after any script specific fallbacks
pub fn common_fallback() -> &'static [&'static str] {
//TODO: abstract style (sans/serif/monospaced)
&[
/* Sans-serif fallbacks */
"Noto Sans",
/* More sans-serif fallbacks */
"DejaVu Sans",
"FreeSans",
/* Mono fallbacks */
"Noto Sans Mono",
"DejaVu Sans Mono",
"FreeMono",
/* Symbols fallbacks */
"Noto Sans Symbols",
"Noto Sans Symbols2",
/* Emoji fallbacks*/
"Noto Color Emoji",
//TODO: Add CJK script here for doublewides?
]
}
// Fallbacks to never use
pub fn forbidden_fallback() -> &'static [&'static str] {
&[]
}
fn han_unification(locale: &str) -> &'static [&'static str] {
match locale {
// Japan
"ja" => &["Noto Sans CJK JP"],
// Korea
"ko" => &["Noto Sans CJK KR"],
// Hong Kong
"zh-HK" => &["Noto Sans CJK HK"],
// Taiwan
"zh-TW" => &["Noto Sans CJK TC"],
// Simplified Chinese is the default (also catches "zh-CN" for China)
_ => &["Noto Sans CJK SC"],
}
}
// Fallbacks to use per script
pub fn script_fallback(script: Script, locale: &str) -> &'static [&'static str] {
//TODO: abstract style (sans/serif/monospaced)
match script {
Script::Adlam => &["Noto Sans Adlam", "Noto Sans Adlam Unjoined"],
Script::Arabic => &["Noto Sans Arabic"],
Script::Armenian => &["Noto Sans Armenian"],
Script::Bengali => &["Noto Sans Bengali"],
Script::Bopomofo => han_unification(locale),
//TODO: DejaVu Sans would typically be selected for braille characters,
// but this breaks alignment when used alongside monospaced text.
// By requesting the use of FreeMono first, this issue can be avoided.
Script::Braille => &["FreeMono"],
Script::Buhid => &["Noto Sans Buhid"],
Script::Chakma => &["Noto Sans Chakma"],
Script::Cherokee => &["Noto Sans Cherokee"],
Script::Deseret => &["Noto Sans Deseret"],
Script::Devanagari => &["Noto Sans Devanagari"],
Script::Ethiopic => &["Noto Sans Ethiopic"],
Script::Georgian => &["Noto Sans Georgian"],
Script::Gothic => &["Noto Sans Gothic"],
Script::Grantha => &["Noto Sans Grantha"],
Script::Gujarati => &["Noto Sans Gujarati"],
Script::Gurmukhi => &["Noto Sans Gurmukhi"],
Script::Han => han_unification(locale),
Script::Hangul => han_unification("ko"),
Script::Hanunoo => &["Noto Sans Hanunoo"],
Script::Hebrew => &["Noto Sans Hebrew"],
Script::Hiragana => han_unification("ja"),
Script::Javanese => &["Noto Sans Javanese"],
Script::Kannada => &["Noto Sans Kannada"],
Script::Katakana => han_unification("ja"),
Script::Khmer => &["Noto Sans Khmer"],
Script::Lao => &["Noto Sans Lao"],
Script::Malayalam => &["Noto Sans Malayalam"],
Script::Mongolian => &["Noto Sans Mongolian"],
Script::Myanmar => &["Noto Sans Myanmar"],
Script::Oriya => &["Noto Sans Oriya"],
Script::Runic => &["Noto Sans Runic"],
Script::Sinhala => &["Noto Sans Sinhala"],
Script::Syriac => &["Noto Sans Syriac"],
Script::Tagalog => &["Noto Sans Tagalog"],
Script::Tagbanwa => &["Noto Sans Tagbanwa"],
Script::Tai_Le => &["Noto Sans Tai Le"],
Script::Tai_Tham => &["Noto Sans Tai Tham"],
Script::Tai_Viet => &["Noto Sans Tai Viet"],
Script::Tamil => &["Noto Sans Tamil"],
Script::Telugu => &["Noto Sans Telugu"],
Script::Thaana => &["Noto Sans Thaana"],
Script::Thai => &["Noto Sans Thai"],
//TODO: no sans script?
Script::Tibetan => &["Noto Serif Tibetan"],
Script::Tifinagh => &["Noto Sans Tifinagh"],
Script::Vai => &["Noto Sans Vai"],
//TODO: Use han_unification?
Script::Yi => &["Noto Sans Yi", "Noto Sans CJK SC"],
_ => &[],
}
}

View File

@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use unicode_script::Script;
// Fallbacks to use after any script specific fallbacks
pub fn common_fallback() -> &'static [&'static str] {
//TODO: abstract style (sans/serif/monospaced)
&[
"Segoe UI",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Segoe UI Historic",
//TODO: Add CJK script here for doublewides?
]
}
// Fallbacks to never use
pub fn forbidden_fallback() -> &'static [&'static str] {
&[]
}
fn han_unification(locale: &str) -> &'static [&'static str] {
//TODO!
match locale {
// Japan
"ja" => &["Yu Gothic"],
// Korea
"ko" => &["Malgun Gothic"],
// Hong Kong"
"zh-HK" => &["MingLiU_HKSCS"],
// Taiwan
"zh-TW" => &["Microsoft JhengHei UI"],
// Simplified Chinese is the default (also catches "zh-CN" for China)
_ => &["Microsoft YaHei UI"],
}
}
// Fallbacks to use per script
pub fn script_fallback(script: Script, locale: &str) -> &'static [&'static str] {
//TODO: better match https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/platform/fonts/win/font_fallback_win.cc#L99
match script {
Script::Adlam => &["Ebrima"],
Script::Bengali => &["Nirmala UI"],
Script::Canadian_Aboriginal => &["Gadugi"],
Script::Chakma => &["Nirmala UI"],
Script::Cherokee => &["Gadugi"],
Script::Devanagari => &["Nirmala UI"],
Script::Ethiopic => &["Ebrima"],
Script::Gujarati => &["Nirmala UI"],
Script::Gurmukhi => &["Nirmala UI"],
Script::Han => han_unification(locale),
Script::Hangul => han_unification("ko"),
Script::Hiragana => han_unification("ja"),
Script::Javanese => &["Javanese Text"],
Script::Kannada => &["Nirmala UI"],
Script::Katakana => han_unification("ja"),
Script::Khmer => &["Leelawadee UI"],
Script::Lao => &["Leelawadee UI"],
Script::Malayalam => &["Nirmala UI"],
Script::Mongolian => &["Mongolian Baiti"],
Script::Myanmar => &["Myanmar Text"],
Script::Oriya => &["Nirmala UI"],
Script::Sinhala => &["Nirmala UI"],
Script::Tamil => &["Nirmala UI"],
Script::Telugu => &["Nirmala UI"],
Script::Thaana => &["MV Boli"],
Script::Thai => &["Leelawadee UI"],
Script::Tibetan => &["Microsoft Himalaya"],
Script::Tifinagh => &["Ebrima"],
Script::Vai => &["Ebrima"],
Script::Yi => &["Microsoft Yi Baiti"],
_ => &[],
}
}

195
vendor/cosmic-text/src/font/mod.rs vendored Normal file
View File

@@ -0,0 +1,195 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pub(crate) mod fallback;
// re-export ttf_parser
pub use ttf_parser;
use core::fmt;
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use rustybuzz::Face as RustybuzzFace;
use self_cell::self_cell;
pub use self::system::*;
mod system;
self_cell!(
struct OwnedFace {
owner: Arc<dyn AsRef<[u8]> + Send + Sync>,
#[covariant]
dependent: RustybuzzFace,
}
);
struct FontMonospaceFallback {
monospace_em_width: Option<f32>,
scripts: Vec<[u8; 4]>,
unicode_codepoints: Vec<u32>,
}
/// A font
pub struct Font {
#[cfg(feature = "swash")]
swash: (u32, swash::CacheKey),
rustybuzz: OwnedFace,
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
id: fontdb::ID,
monospace_fallback: Option<FontMonospaceFallback>,
}
impl fmt::Debug for Font {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Font")
.field("id", &self.id)
.finish_non_exhaustive()
}
}
impl Font {
pub fn id(&self) -> fontdb::ID {
self.id
}
pub fn monospace_em_width(&self) -> Option<f32> {
self.monospace_fallback
.as_ref()
.and_then(|x| x.monospace_em_width)
}
pub fn scripts(&self) -> &[[u8; 4]] {
self.monospace_fallback.as_ref().map_or(&[], |x| &x.scripts)
}
pub fn unicode_codepoints(&self) -> &[u32] {
self.monospace_fallback
.as_ref()
.map_or(&[], |x| &x.unicode_codepoints)
}
pub fn data(&self) -> &[u8] {
(*self.data).as_ref()
}
pub fn rustybuzz(&self) -> &RustybuzzFace<'_> {
self.rustybuzz.borrow_dependent()
}
#[cfg(feature = "swash")]
pub fn as_swash(&self) -> swash::FontRef<'_> {
let swash = &self.swash;
swash::FontRef {
data: self.data(),
offset: swash.0,
key: swash.1,
}
}
}
impl Font {
pub fn new(db: &fontdb::Database, id: fontdb::ID) -> Option<Self> {
let info = db.face(id)?;
let monospace_fallback = if cfg!(feature = "monospace_fallback") {
db.with_face_data(id, |font_data, face_index| {
let face = ttf_parser::Face::parse(font_data, face_index).ok()?;
let monospace_em_width = info
.monospaced
.then(|| {
let hor_advance = face.glyph_hor_advance(face.glyph_index(' ')?)? as f32;
let upem = face.units_per_em() as f32;
Some(hor_advance / upem)
})
.flatten();
if info.monospaced && monospace_em_width.is_none() {
None?;
}
let scripts = face
.tables()
.gpos
.into_iter()
.chain(face.tables().gsub)
.flat_map(|table| table.scripts)
.map(|script| script.tag.to_bytes())
.collect();
let mut unicode_codepoints = Vec::new();
face.tables()
.cmap?
.subtables
.into_iter()
.filter(|subtable| subtable.is_unicode())
.for_each(|subtable| {
unicode_codepoints.reserve(1024);
subtable.codepoints(|code_point| {
if subtable.glyph_index(code_point).is_some() {
unicode_codepoints.push(code_point);
}
});
});
unicode_codepoints.shrink_to_fit();
Some(FontMonospaceFallback {
monospace_em_width,
scripts,
unicode_codepoints,
})
})?
} else {
None
};
let data = match &info.source {
fontdb::Source::Binary(data) => Arc::clone(data),
#[cfg(feature = "std")]
fontdb::Source::File(path) => {
log::warn!("Unsupported fontdb Source::File('{}')", path.display());
return None;
}
#[cfg(feature = "std")]
fontdb::Source::SharedFile(_path, data) => Arc::clone(data),
};
Some(Self {
id: info.id,
monospace_fallback,
#[cfg(feature = "swash")]
swash: {
let swash = swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
(swash.offset, swash.key)
},
rustybuzz: OwnedFace::try_new(Arc::clone(&data), |data| {
RustybuzzFace::from_slice((**data).as_ref(), info.index).ok_or(())
})
.ok()?,
data,
})
}
}
#[cfg(test)]
mod test {
#[test]
fn test_fonts_load_time() {
use crate::FontSystem;
use sys_locale::get_locale;
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
let mut db = fontdb::Database::new();
let locale = get_locale().expect("Local available");
db.load_system_fonts();
FontSystem::new_with_locale_and_db(locale, db);
#[cfg(not(target_arch = "wasm32"))]
println!("Fonts load time {}ms.", now.elapsed().as_millis());
}
}

387
vendor/cosmic-text/src/font/system.rs vendored Normal file
View File

@@ -0,0 +1,387 @@
use crate::{Attrs, Font, FontMatchAttrs, HashMap, ShapeBuffer};
use alloc::collections::BTreeSet;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use core::ops::{Deref, DerefMut};
// re-export fontdb and rustybuzz
pub use fontdb;
pub use rustybuzz;
use super::fallback::MonospaceFallbackInfo;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FontMatchKey {
pub(crate) font_weight_diff: u16,
pub(crate) font_weight: u16,
pub(crate) id: fontdb::ID,
}
struct FontCachedCodepointSupportInfo {
supported: Vec<u32>,
not_supported: Vec<u32>,
}
impl FontCachedCodepointSupportInfo {
const SUPPORTED_MAX_SZ: usize = 512;
const NOT_SUPPORTED_MAX_SZ: usize = 1024;
fn new() -> Self {
Self {
supported: Vec::with_capacity(Self::SUPPORTED_MAX_SZ),
not_supported: Vec::with_capacity(Self::NOT_SUPPORTED_MAX_SZ),
}
}
#[inline(always)]
fn unknown_has_codepoint(
&mut self,
font_codepoints: &[u32],
codepoint: u32,
supported_insert_pos: usize,
not_supported_insert_pos: usize,
) -> bool {
let ret = font_codepoints.contains(&codepoint);
if ret {
// don't bother inserting if we are going to truncate the entry away
if supported_insert_pos != Self::SUPPORTED_MAX_SZ {
self.supported.insert(supported_insert_pos, codepoint);
self.supported.truncate(Self::SUPPORTED_MAX_SZ);
}
} else {
// don't bother inserting if we are going to truncate the entry away
if not_supported_insert_pos != Self::NOT_SUPPORTED_MAX_SZ {
self.not_supported
.insert(not_supported_insert_pos, codepoint);
self.not_supported.truncate(Self::NOT_SUPPORTED_MAX_SZ);
}
}
ret
}
#[inline(always)]
fn has_codepoint(&mut self, font_codepoints: &[u32], codepoint: u32) -> bool {
match self.supported.binary_search(&codepoint) {
Ok(_) => true,
Err(supported_insert_pos) => match self.not_supported.binary_search(&codepoint) {
Ok(_) => false,
Err(not_supported_insert_pos) => self.unknown_has_codepoint(
font_codepoints,
codepoint,
supported_insert_pos,
not_supported_insert_pos,
),
},
}
}
}
/// Access to the system fonts.
pub struct FontSystem {
/// The locale of the system.
locale: String,
/// The underlying font database.
db: fontdb::Database,
/// Cache for loaded fonts from the database.
font_cache: HashMap<fontdb::ID, Option<Arc<Font>>>,
/// Sorted unique ID's of all Monospace fonts in DB
monospace_font_ids: Vec<fontdb::ID>,
/// Sorted unique ID's of all Monospace fonts in DB per script.
/// A font may support multiple scripts of course, so the same ID
/// may appear in multiple map value vecs.
per_script_monospace_font_ids: HashMap<[u8; 4], Vec<fontdb::ID>>,
/// Cache for font codepoint support info
font_codepoint_support_info_cache: HashMap<fontdb::ID, FontCachedCodepointSupportInfo>,
/// Cache for font matches.
font_matches_cache: HashMap<FontMatchAttrs, Arc<Vec<FontMatchKey>>>,
/// Scratch buffer for shaping and laying out.
pub(crate) shape_buffer: ShapeBuffer,
/// Buffer for use in `FontFallbackIter`.
pub(crate) monospace_fallbacks_buffer: BTreeSet<MonospaceFallbackInfo>,
/// Cache for shaped runs
#[cfg(feature = "shape-run-cache")]
pub shape_run_cache: crate::ShapeRunCache,
}
impl fmt::Debug for FontSystem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FontSystem")
.field("locale", &self.locale)
.field("db", &self.db)
.finish()
}
}
impl FontSystem {
const FONT_MATCHES_CACHE_SIZE_LIMIT: usize = 256;
/// Create a new [`FontSystem`], that allows access to any installed system fonts
///
/// # Timing
///
/// This function takes some time to run. On the release build, it can take up to a second,
/// while debug builds can take up to ten times longer. For this reason, it should only be
/// called once, and the resulting [`FontSystem`] should be shared.
pub fn new() -> Self {
Self::new_with_fonts(core::iter::empty())
}
/// Create a new [`FontSystem`] with a pre-specified set of fonts.
pub fn new_with_fonts(fonts: impl IntoIterator<Item = fontdb::Source>) -> Self {
let locale = Self::get_locale();
log::debug!("Locale: {}", locale);
let mut db = fontdb::Database::new();
Self::load_fonts(&mut db, fonts.into_iter());
//TODO: configurable default fonts
db.set_monospace_family("Noto Sans Mono");
db.set_sans_serif_family("Open Sans");
db.set_serif_family("DejaVu Serif");
Self::new_with_locale_and_db(locale, db)
}
/// Create a new [`FontSystem`] with a pre-specified locale and font database.
pub fn new_with_locale_and_db(locale: String, db: fontdb::Database) -> Self {
let mut monospace_font_ids = db
.faces()
.filter(|face_info| {
face_info.monospaced && !face_info.post_script_name.contains("Emoji")
})
.map(|face_info| face_info.id)
.collect::<Vec<_>>();
monospace_font_ids.sort();
let mut per_script_monospace_font_ids: HashMap<[u8; 4], BTreeSet<fontdb::ID>> =
HashMap::default();
if cfg!(feature = "monospace_fallback") {
monospace_font_ids.iter().for_each(|&id| {
db.with_face_data(id, |font_data, face_index| {
let _ = ttf_parser::Face::parse(font_data, face_index).map(|face| {
face.tables()
.gpos
.into_iter()
.chain(face.tables().gsub)
.flat_map(|table| table.scripts)
.inspect(|script| {
per_script_monospace_font_ids
.entry(script.tag.to_bytes())
.or_default()
.insert(id);
})
});
});
});
}
let per_script_monospace_font_ids = per_script_monospace_font_ids
.into_iter()
.map(|(k, v)| (k, Vec::from_iter(v)))
.collect();
Self {
locale,
db,
monospace_font_ids,
per_script_monospace_font_ids,
font_cache: Default::default(),
font_matches_cache: Default::default(),
font_codepoint_support_info_cache: Default::default(),
monospace_fallbacks_buffer: BTreeSet::default(),
#[cfg(feature = "shape-run-cache")]
shape_run_cache: crate::ShapeRunCache::default(),
shape_buffer: ShapeBuffer::default(),
}
}
/// Get the locale.
pub fn locale(&self) -> &str {
&self.locale
}
/// Get the database.
pub fn db(&self) -> &fontdb::Database {
&self.db
}
/// Get a mutable reference to the database.
pub fn db_mut(&mut self) -> &mut fontdb::Database {
self.font_matches_cache.clear();
&mut self.db
}
/// Consume this [`FontSystem`] and return the locale and database.
pub fn into_locale_and_db(self) -> (String, fontdb::Database) {
(self.locale, self.db)
}
/// Get a font by its ID.
pub fn get_font(&mut self, id: fontdb::ID) -> Option<Arc<Font>> {
self.font_cache
.entry(id)
.or_insert_with(|| {
#[cfg(feature = "std")]
unsafe {
self.db.make_shared_face_data(id);
}
match Font::new(&self.db, id) {
Some(font) => Some(Arc::new(font)),
None => {
log::warn!(
"failed to load font '{}'",
self.db.face(id)?.post_script_name
);
None
}
}
})
.clone()
}
pub fn is_monospace(&self, id: fontdb::ID) -> bool {
self.monospace_font_ids.binary_search(&id).is_ok()
}
pub fn get_monospace_ids_for_scripts(
&self,
scripts: impl Iterator<Item = [u8; 4]>,
) -> Vec<fontdb::ID> {
let mut ret = scripts
.filter_map(|script| self.per_script_monospace_font_ids.get(&script))
.flat_map(|ids| ids.iter().copied())
.collect::<Vec<_>>();
ret.sort();
ret.dedup();
ret
}
#[inline(always)]
pub fn get_font_supported_codepoints_in_word(
&mut self,
id: fontdb::ID,
word: &str,
) -> Option<usize> {
self.get_font(id).map(|font| {
let code_points = font.unicode_codepoints();
let cache = self
.font_codepoint_support_info_cache
.entry(id)
.or_insert_with(FontCachedCodepointSupportInfo::new);
word.chars()
.filter(|ch| cache.has_codepoint(code_points, u32::from(*ch)))
.count()
})
}
pub fn get_font_matches(&mut self, attrs: Attrs<'_>) -> Arc<Vec<FontMatchKey>> {
// Clear the cache first if it reached the size limit
if self.font_matches_cache.len() >= Self::FONT_MATCHES_CACHE_SIZE_LIMIT {
log::trace!("clear font mache cache");
self.font_matches_cache.clear();
}
self.font_matches_cache
//TODO: do not create AttrsOwned unless entry does not already exist
.entry(attrs.into())
.or_insert_with(|| {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
let now = std::time::Instant::now();
let mut font_match_keys = self
.db
.faces()
.filter(|face| attrs.matches(face))
.map(|face| FontMatchKey {
font_weight_diff: attrs.weight.0.abs_diff(face.weight.0),
font_weight: face.weight.0,
id: face.id,
})
.collect::<Vec<_>>();
// Sort so we get the keys with weight_offset=0 first
font_match_keys.sort();
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
{
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
}
Arc::new(font_match_keys)
})
.clone()
}
#[cfg(feature = "std")]
fn get_locale() -> String {
sys_locale::get_locale().unwrap_or_else(|| {
log::warn!("failed to get system locale, falling back to en-US");
String::from("en-US")
})
}
#[cfg(not(feature = "std"))]
fn get_locale() -> String {
String::from("en-US")
}
#[cfg(feature = "std")]
fn load_fonts(db: &mut fontdb::Database, fonts: impl Iterator<Item = fontdb::Source>) {
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
db.load_system_fonts();
for source in fonts {
db.load_font_source(source);
}
#[cfg(not(target_arch = "wasm32"))]
log::debug!(
"Parsed {} font faces in {}ms.",
db.len(),
now.elapsed().as_millis()
);
}
#[cfg(not(feature = "std"))]
fn load_fonts(db: &mut fontdb::Database, fonts: impl Iterator<Item = fontdb::Source>) {
for source in fonts {
db.load_font_source(source);
}
}
}
/// A value borrowed together with an [`FontSystem`]
#[derive(Debug)]
pub struct BorrowedWithFontSystem<'a, T> {
pub(crate) inner: &'a mut T,
pub(crate) font_system: &'a mut FontSystem,
}
impl<T> Deref for BorrowedWithFontSystem<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<T> DerefMut for BorrowedWithFontSystem<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}

162
vendor/cosmic-text/src/glyph_cache.rs vendored Normal file
View File

@@ -0,0 +1,162 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
bitflags::bitflags! {
/// Flags that change rendering
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct CacheKeyFlags: u32 {
/// Skew by 14 degrees to synthesize italic
const FAKE_ITALIC = 1;
}
}
/// Key for building a glyph cache
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CacheKey {
/// Font ID
pub font_id: fontdb::ID,
/// Glyph ID
pub glyph_id: u16,
/// `f32` bits of font size
pub font_size_bits: u32,
/// Binning of fractional X offset
pub x_bin: SubpixelBin,
/// Binning of fractional Y offset
pub y_bin: SubpixelBin,
/// [`CacheKeyFlags`]
pub flags: CacheKeyFlags,
}
impl CacheKey {
pub fn new(
font_id: fontdb::ID,
glyph_id: u16,
font_size: f32,
pos: (f32, f32),
flags: CacheKeyFlags,
) -> (Self, i32, i32) {
let (x, x_bin) = SubpixelBin::new(pos.0);
let (y, y_bin) = SubpixelBin::new(pos.1);
(
Self {
font_id,
glyph_id,
font_size_bits: font_size.to_bits(),
x_bin,
y_bin,
flags,
},
x,
y,
)
}
}
/// Binning of subpixel position for cache optimization
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum SubpixelBin {
Zero,
One,
Two,
Three,
}
impl SubpixelBin {
pub fn new(pos: f32) -> (i32, Self) {
let trunc = pos as i32;
let fract = pos - trunc as f32;
if pos.is_sign_negative() {
if fract > -0.125 {
(trunc, Self::Zero)
} else if fract > -0.375 {
(trunc - 1, Self::Three)
} else if fract > -0.625 {
(trunc - 1, Self::Two)
} else if fract > -0.875 {
(trunc - 1, Self::One)
} else {
(trunc - 1, Self::Zero)
}
} else {
#[allow(clippy::collapsible_else_if)]
if fract < 0.125 {
(trunc, Self::Zero)
} else if fract < 0.375 {
(trunc, Self::One)
} else if fract < 0.625 {
(trunc, Self::Two)
} else if fract < 0.875 {
(trunc, Self::Three)
} else {
(trunc + 1, Self::Zero)
}
}
}
pub fn as_float(&self) -> f32 {
match self {
Self::Zero => 0.0,
Self::One => 0.25,
Self::Two => 0.5,
Self::Three => 0.75,
}
}
}
#[test]
fn test_subpixel_bins() {
// POSITIVE TESTS
// Maps to 0.0
assert_eq!(SubpixelBin::new(0.0), (0, SubpixelBin::Zero));
assert_eq!(SubpixelBin::new(0.124), (0, SubpixelBin::Zero));
// Maps to 0.25
assert_eq!(SubpixelBin::new(0.125), (0, SubpixelBin::One));
assert_eq!(SubpixelBin::new(0.25), (0, SubpixelBin::One));
assert_eq!(SubpixelBin::new(0.374), (0, SubpixelBin::One));
// Maps to 0.5
assert_eq!(SubpixelBin::new(0.375), (0, SubpixelBin::Two));
assert_eq!(SubpixelBin::new(0.5), (0, SubpixelBin::Two));
assert_eq!(SubpixelBin::new(0.624), (0, SubpixelBin::Two));
// Maps to 0.75
assert_eq!(SubpixelBin::new(0.625), (0, SubpixelBin::Three));
assert_eq!(SubpixelBin::new(0.75), (0, SubpixelBin::Three));
assert_eq!(SubpixelBin::new(0.874), (0, SubpixelBin::Three));
// Maps to 1.0
assert_eq!(SubpixelBin::new(0.875), (1, SubpixelBin::Zero));
assert_eq!(SubpixelBin::new(0.999), (1, SubpixelBin::Zero));
assert_eq!(SubpixelBin::new(1.0), (1, SubpixelBin::Zero));
assert_eq!(SubpixelBin::new(1.124), (1, SubpixelBin::Zero));
// NEGATIVE TESTS
// Maps to 0.0
assert_eq!(SubpixelBin::new(-0.0), (0, SubpixelBin::Zero));
assert_eq!(SubpixelBin::new(-0.124), (0, SubpixelBin::Zero));
// Maps to 0.25
assert_eq!(SubpixelBin::new(-0.125), (-1, SubpixelBin::Three));
assert_eq!(SubpixelBin::new(-0.25), (-1, SubpixelBin::Three));
assert_eq!(SubpixelBin::new(-0.374), (-1, SubpixelBin::Three));
// Maps to 0.5
assert_eq!(SubpixelBin::new(-0.375), (-1, SubpixelBin::Two));
assert_eq!(SubpixelBin::new(-0.5), (-1, SubpixelBin::Two));
assert_eq!(SubpixelBin::new(-0.624), (-1, SubpixelBin::Two));
// Maps to 0.75
assert_eq!(SubpixelBin::new(-0.625), (-1, SubpixelBin::One));
assert_eq!(SubpixelBin::new(-0.75), (-1, SubpixelBin::One));
assert_eq!(SubpixelBin::new(-0.874), (-1, SubpixelBin::One));
// Maps to 1.0
assert_eq!(SubpixelBin::new(-0.875), (-1, SubpixelBin::Zero));
assert_eq!(SubpixelBin::new(-0.999), (-1, SubpixelBin::Zero));
assert_eq!(SubpixelBin::new(-1.0), (-1, SubpixelBin::Zero));
assert_eq!(SubpixelBin::new(-1.124), (-1, SubpixelBin::Zero));
}

148
vendor/cosmic-text/src/layout.rs vendored Normal file
View File

@@ -0,0 +1,148 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use core::fmt::Display;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::{math, CacheKey, CacheKeyFlags, Color};
/// A laid out glyph
#[derive(Clone, Debug)]
pub struct LayoutGlyph {
/// Start index of cluster in original line
pub start: usize,
/// End index of cluster in original line
pub end: usize,
/// Font size of the glyph
pub font_size: f32,
/// Line height of the glyph, will override buffer setting
pub line_height_opt: Option<f32>,
/// Font id of the glyph
pub font_id: fontdb::ID,
/// Font id of the glyph
pub glyph_id: u16,
/// X offset of hitbox
pub x: f32,
/// Y offset of hitbox
pub y: f32,
/// Width of hitbox
pub w: f32,
/// Unicode `BiDi` embedding level, character is left-to-right if `level` is divisible by 2
pub level: unicode_bidi::Level,
/// X offset in line
///
/// If you are dealing with physical coordinates, use [`Self::physical`] to obtain a
/// [`PhysicalGlyph`] for rendering.
///
/// This offset is useful when you are dealing with logical units and you do not care or
/// cannot guarantee pixel grid alignment. For instance, when you want to use the glyphs
/// for vectorial text, apply linear transformations to the layout, etc.
pub x_offset: f32,
/// Y offset in line
///
/// If you are dealing with physical coordinates, use [`Self::physical`] to obtain a
/// [`PhysicalGlyph`] for rendering.
///
/// This offset is useful when you are dealing with logical units and you do not care or
/// cannot guarantee pixel grid alignment. For instance, when you want to use the glyphs
/// for vectorial text, apply linear transformations to the layout, etc.
pub y_offset: f32,
/// Optional color override
pub color_opt: Option<Color>,
/// Metadata from `Attrs`
pub metadata: usize,
/// [`CacheKeyFlags`]
pub cache_key_flags: CacheKeyFlags,
}
#[derive(Clone, Debug)]
pub struct PhysicalGlyph {
/// Cache key, see [`CacheKey`]
pub cache_key: CacheKey,
/// Integer component of X offset in line
pub x: i32,
/// Integer component of Y offset in line
pub y: i32,
}
impl LayoutGlyph {
pub fn physical(&self, offset: (f32, f32), scale: f32) -> PhysicalGlyph {
let x_offset = self.font_size * self.x_offset;
let y_offset = self.font_size * self.y_offset;
let (cache_key, x, y) = CacheKey::new(
self.font_id,
self.glyph_id,
self.font_size * scale,
(
(self.x + x_offset) * scale + offset.0,
math::truncf((self.y - y_offset) * scale + offset.1), // Hinting in Y axis
),
self.cache_key_flags,
);
PhysicalGlyph { cache_key, x, y }
}
}
/// A line of laid out glyphs
#[derive(Clone, Debug)]
pub struct LayoutLine {
/// Width of the line
pub w: f32,
/// Maximum ascent of the glyphs in line
pub max_ascent: f32,
/// Maximum descent of the glyphs in line
pub max_descent: f32,
/// Maximum line height of any spans in line
pub line_height_opt: Option<f32>,
/// Glyphs in line
pub glyphs: Vec<LayoutGlyph>,
}
/// Wrapping mode
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Wrap {
/// No wrapping
None,
/// Wraps at a glyph level
Glyph,
/// Wraps at the word level
Word,
/// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself
WordOrGlyph,
}
impl Display for Wrap {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::None => write!(f, "No Wrap"),
Self::Word => write!(f, "Word Wrap"),
Self::WordOrGlyph => write!(f, "Word Wrap or Character"),
Self::Glyph => write!(f, "Character"),
}
}
}
/// Align or justify
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Align {
Left,
Right,
Center,
Justified,
End,
}
impl Display for Align {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Left => write!(f, "Left"),
Self::Right => write!(f, "Right"),
Self::Center => write!(f, "Center"),
Self::Justified => write!(f, "Justified"),
Self::End => write!(f, "End"),
}
}
}

150
vendor/cosmic-text/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,150 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//! # COSMIC Text
//!
//! This library provides advanced text handling in a generic way. It provides abstractions for
//! shaping, font discovery, font fallback, layout, rasterization, and editing. Shaping utilizes
//! rustybuzz, font discovery utilizes fontdb, and the rasterization is optional and utilizes
//! swash. The other features are developed internal to this library.
//!
//! It is recommended that you start by creating a [`FontSystem`], after which you can create a
//! [`Buffer`], provide it with some text, and then inspect the layout it produces. At this
//! point, you can use the `SwashCache` to rasterize glyphs into either images or pixels.
//!
//! ```
//! use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics, Shaping};
//!
//! // A FontSystem provides access to detected system fonts, create one per application
//! let mut font_system = FontSystem::new();
//!
//! // A SwashCache stores rasterized glyphs, create one per application
//! let mut swash_cache = SwashCache::new();
//!
//! // Text metrics indicate the font size and line height of a buffer
//! let metrics = Metrics::new(14.0, 20.0);
//!
//! // A Buffer provides shaping and layout for a UTF-8 string, create one per text widget
//! let mut buffer = Buffer::new(&mut font_system, metrics);
//!
//! // Borrow buffer together with the font system for more convenient method calls
//! let mut buffer = buffer.borrow_with(&mut font_system);
//!
//! // Set a size for the text buffer, in pixels
//! buffer.set_size(Some(80.0), Some(25.0));
//!
//! // Attributes indicate what font to choose
//! let attrs = Attrs::new();
//!
//! // Add some text!
//! buffer.set_text("Hello, Rust! 🦀\n", attrs, Shaping::Advanced);
//!
//! // Perform shaping as desired
//! buffer.shape_until_scroll(true);
//!
//! // Inspect the output runs
//! for run in buffer.layout_runs() {
//! for glyph in run.glyphs.iter() {
//! println!("{:#?}", glyph);
//! }
//! }
//!
//! // Create a default text color
//! let text_color = Color::rgb(0xFF, 0xFF, 0xFF);
//!
//! // Draw the buffer (for performance, instead use SwashCache directly)
//! buffer.draw(&mut swash_cache, text_color, |x, y, w, h, color| {
//! // Fill in your code here for drawing rectangles
//! });
//! ```
// Not interested in these lints
#![allow(clippy::new_without_default)]
// TODO: address occurrences and then deny
//
// Overflows can produce unpredictable results and are only checked in debug builds
#![allow(clippy::arithmetic_side_effects)]
// Indexing a slice can cause panics and that is something we always want to avoid
#![allow(clippy::indexing_slicing)]
// Soundness issues
//
// Dereferencing unaligned pointers may be undefined behavior
#![deny(clippy::cast_ptr_alignment)]
// Avoid panicking in without information about the panic. Use expect
#![deny(clippy::unwrap_used)]
// Ensure all types have a debug impl
#![deny(missing_debug_implementations)]
// This is usually a serious issue - a missing import of a define where it is interpreted
// as a catch-all variable in a match, for example
#![deny(unreachable_patterns)]
// Ensure that all must_use results are used
#![deny(unused_must_use)]
// Style issues
//
// Documentation not ideal
#![warn(clippy::doc_markdown)]
// Document possible errors
#![warn(clippy::missing_errors_doc)]
// Document possible panics
#![warn(clippy::missing_panics_doc)]
// Ensure semicolons are present
#![warn(clippy::semicolon_if_nothing_returned)]
// Ensure numbers are readable
#![warn(clippy::unreadable_literal)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(not(any(feature = "std", feature = "no_std")))]
compile_error!("Either the `std` or `no_std` feature must be enabled");
pub use self::attrs::*;
mod attrs;
pub use self::bidi_para::*;
mod bidi_para;
pub use self::buffer::*;
mod buffer;
pub use self::buffer_line::*;
mod buffer_line;
pub use self::cached::*;
mod cached;
pub use self::glyph_cache::*;
mod glyph_cache;
pub use self::cursor::*;
mod cursor;
pub use self::edit::*;
mod edit;
pub use self::font::*;
mod font;
pub use self::layout::*;
mod layout;
pub use self::line_ending::*;
mod line_ending;
pub use self::shape::*;
mod shape;
pub use self::shape_run_cache::*;
mod shape_run_cache;
#[cfg(feature = "swash")]
pub use self::swash::*;
#[cfg(feature = "swash")]
mod swash;
mod math;
type BuildHasher = core::hash::BuildHasherDefault<rustc_hash::FxHasher>;
#[cfg(feature = "std")]
type HashMap<K, V> = std::collections::HashMap<K, V, BuildHasher>;
#[cfg(not(feature = "std"))]
type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasher>;

98
vendor/cosmic-text/src/line_ending.rs vendored Normal file
View File

@@ -0,0 +1,98 @@
use core::ops::Range;
/// Line ending
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum LineEnding {
/// Use `\n` for line ending (POSIX-style)
#[default]
Lf,
/// Use `\r\n` for line ending (Windows-style)
CrLf,
/// Use `\r` for line ending (many legacy systems)
Cr,
/// Use `\n\r` for line ending (some legacy systems)
LfCr,
/// No line ending
None,
}
impl LineEnding {
/// Get the line ending as a str
pub fn as_str(&self) -> &'static str {
match self {
Self::Lf => "\n",
Self::CrLf => "\r\n",
Self::Cr => "\r",
Self::LfCr => "\n\r",
Self::None => "",
}
}
}
/// Iterator over lines terminated by [`LineEnding`]
#[derive(Debug)]
pub struct LineIter<'a> {
string: &'a str,
start: usize,
end: usize,
}
impl<'a> LineIter<'a> {
/// Create an iterator of lines in a string slice
pub fn new(string: &'a str) -> Self {
Self {
string,
start: 0,
end: string.len(),
}
}
}
impl Iterator for LineIter<'_> {
type Item = (Range<usize>, LineEnding);
fn next(&mut self) -> Option<Self::Item> {
let start = self.start;
match self.string[start..self.end].find(['\r', '\n']) {
Some(i) => {
let end = start + i;
self.start = end;
let after = &self.string[end..];
let ending = if after.starts_with("\r\n") {
LineEnding::CrLf
} else if after.starts_with("\n\r") {
LineEnding::LfCr
} else if after.starts_with("\n") {
LineEnding::Lf
} else if after.starts_with("\r") {
LineEnding::Cr
} else {
//TODO: this should not be possible
LineEnding::None
};
self.start += ending.as_str().len();
Some((start..end, ending))
}
None => {
if self.start < self.end {
self.start = self.end;
Some((start..self.end, LineEnding::None))
} else {
None
}
}
}
}
}
//TODO: DoubleEndedIterator
#[test]
fn test_line_iter() {
let string = "LF\nCRLF\r\nCR\rLFCR\n\rNONE";
let mut iter = LineIter::new(string);
assert_eq!(iter.next(), Some((0..2, LineEnding::Lf)));
assert_eq!(iter.next(), Some((3..7, LineEnding::CrLf)));
assert_eq!(iter.next(), Some((9..11, LineEnding::Cr)));
assert_eq!(iter.next(), Some((12..16, LineEnding::LfCr)));
assert_eq!(iter.next(), Some((18..22, LineEnding::None)));
}

20
vendor/cosmic-text/src/math.rs vendored Normal file
View File

@@ -0,0 +1,20 @@
#[cfg(not(feature = "std"))]
pub use libm::{floorf, roundf, truncf};
#[cfg(feature = "std")]
#[inline]
pub fn floorf(x: f32) -> f32 {
x.floor()
}
#[cfg(feature = "std")]
#[inline]
pub fn roundf(x: f32) -> f32 {
x.round()
}
#[cfg(feature = "std")]
#[inline]
pub fn truncf(x: f32) -> f32 {
x.trunc()
}

1630
vendor/cosmic-text/src/shape.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::ops::Range;
use crate::{AttrsOwned, HashMap, ShapeGlyph};
/// Key for caching shape runs.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ShapeRunKey {
pub text: String,
pub default_attrs: AttrsOwned,
pub attrs_spans: Vec<(Range<usize>, AttrsOwned)>,
}
/// A helper structure for caching shape runs.
#[derive(Clone, Default)]
pub struct ShapeRunCache {
age: u64,
cache: HashMap<ShapeRunKey, (u64, Vec<ShapeGlyph>)>,
}
impl ShapeRunCache {
/// Get cache item, updating age if found
pub fn get(&mut self, key: &ShapeRunKey) -> Option<&Vec<ShapeGlyph>> {
self.cache.get_mut(key).map(|(age, glyphs)| {
*age = self.age;
&*glyphs
})
}
/// Insert cache item with current age
pub fn insert(&mut self, key: ShapeRunKey, glyphs: Vec<ShapeGlyph>) {
self.cache.insert(key, (self.age, glyphs));
}
/// Remove anything in the cache with an age older than `keep_ages`
pub fn trim(&mut self, keep_ages: u64) {
self.cache
.retain(|_key, (age, _glyphs)| *age + keep_ages >= self.age);
// Increase age
self.age += 1;
}
}
impl core::fmt::Debug for ShapeRunCache {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("ShapeRunCache").finish()
}
}

214
vendor/cosmic-text/src/swash.rs vendored Normal file
View File

@@ -0,0 +1,214 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::fmt;
use swash::scale::{image::Content, ScaleContext};
use swash::scale::{Render, Source, StrikeWith};
use swash::zeno::{Format, Vector};
use crate::{CacheKey, CacheKeyFlags, Color, FontSystem, HashMap};
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
pub use swash::zeno::{Angle, Command, Placement, Transform};
fn swash_image(
font_system: &mut FontSystem,
context: &mut ScaleContext,
cache_key: CacheKey,
) -> Option<SwashImage> {
let font = match font_system.get_font(cache_key.font_id) {
Some(some) => some,
None => {
log::warn!("did not find font {:?}", cache_key.font_id);
return None;
}
};
// Build the scaler
let mut scaler = context
.builder(font.as_swash())
.size(f32::from_bits(cache_key.font_size_bits))
.hint(true)
.build();
// Compute the fractional offset-- you'll likely want to quantize this
// in a real renderer
let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
// Select our source order
Render::new(&[
// Color outline with the first palette
Source::ColorOutline(0),
// Color bitmap with best fit selection mode
Source::ColorBitmap(StrikeWith::BestFit),
// Standard scalable outline
Source::Outline,
])
// Select a subpixel format
.format(Format::Alpha)
// Apply the fractional offset
.offset(offset)
.transform(if cache_key.flags.contains(CacheKeyFlags::FAKE_ITALIC) {
Some(Transform::skew(
Angle::from_degrees(14.0),
Angle::from_degrees(0.0),
))
} else {
None
})
// Render the image
.render(&mut scaler, cache_key.glyph_id)
}
fn swash_outline_commands(
font_system: &mut FontSystem,
context: &mut ScaleContext,
cache_key: CacheKey,
) -> Option<Box<[swash::zeno::Command]>> {
use swash::zeno::PathData as _;
let font = match font_system.get_font(cache_key.font_id) {
Some(some) => some,
None => {
log::warn!("did not find font {:?}", cache_key.font_id);
return None;
}
};
// Build the scaler
let mut scaler = context
.builder(font.as_swash())
.size(f32::from_bits(cache_key.font_size_bits))
.hint(true)
.build();
// Scale the outline
let outline = scaler
.scale_outline(cache_key.glyph_id)
.or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
// Get the path information of the outline
let path = outline.path();
// Return the commands
Some(path.commands().collect())
}
/// Cache for rasterizing with the swash scaler
pub struct SwashCache {
context: ScaleContext,
pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
pub outline_command_cache: HashMap<CacheKey, Option<Box<[swash::zeno::Command]>>>,
}
impl fmt::Debug for SwashCache {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("SwashCache { .. }")
}
}
impl SwashCache {
/// Create a new swash cache
pub fn new() -> Self {
Self {
context: ScaleContext::new(),
image_cache: HashMap::default(),
outline_command_cache: HashMap::default(),
}
}
/// Create a swash Image from a cache key, without caching results
pub fn get_image_uncached(
&mut self,
font_system: &mut FontSystem,
cache_key: CacheKey,
) -> Option<SwashImage> {
swash_image(font_system, &mut self.context, cache_key)
}
/// Create a swash Image from a cache key, caching results
pub fn get_image(
&mut self,
font_system: &mut FontSystem,
cache_key: CacheKey,
) -> &Option<SwashImage> {
self.image_cache
.entry(cache_key)
.or_insert_with(|| swash_image(font_system, &mut self.context, cache_key))
}
/// Creates outline commands
pub fn get_outline_commands(
&mut self,
font_system: &mut FontSystem,
cache_key: CacheKey,
) -> Option<&[swash::zeno::Command]> {
self.outline_command_cache
.entry(cache_key)
.or_insert_with(|| swash_outline_commands(font_system, &mut self.context, cache_key))
.as_deref()
}
/// Creates outline commands, without caching results
pub fn get_outline_commands_uncached(
&mut self,
font_system: &mut FontSystem,
cache_key: CacheKey,
) -> Option<Box<[swash::zeno::Command]>> {
swash_outline_commands(font_system, &mut self.context, cache_key)
}
/// Enumerate pixels in an Image, use `with_image` for better performance
pub fn with_pixels<F: FnMut(i32, i32, Color)>(
&mut self,
font_system: &mut FontSystem,
cache_key: CacheKey,
base: Color,
mut f: F,
) {
if let Some(image) = self.get_image(font_system, cache_key) {
let x = image.placement.left;
let y = -image.placement.top;
match image.content {
Content::Mask => {
let mut i = 0;
for off_y in 0..image.placement.height as i32 {
for off_x in 0..image.placement.width as i32 {
//TODO: blend base alpha?
f(
x + off_x,
y + off_y,
Color(((image.data[i] as u32) << 24) | base.0 & 0xFF_FF_FF),
);
i += 1;
}
}
}
Content::Color => {
let mut i = 0;
for off_y in 0..image.placement.height as i32 {
for off_x in 0..image.placement.width as i32 {
//TODO: blend base alpha?
f(
x + off_x,
y + off_y,
Color::rgba(
image.data[i],
image.data[i + 1],
image.data[i + 2],
image.data[i + 3],
),
);
i += 4;
}
}
}
Content::SubpixelMask => {
log::warn!("TODO: SubpixelMask");
}
}
}
}
}

1
vendor/cosmic-text/terminal.sh vendored Executable file
View File

@@ -0,0 +1 @@
RUST_LOG=cosmic_text=debug,terminal=debug cargo run --release --package terminal -- "$@"

18
vendor/cosmic-text/test.sh vendored Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -ex
echo Run CI script
./ci.sh
echo Build documentation
cargo doc
echo Build all examples
cargo build --release --all
echo Run terminal example
target/release/terminal
echo Run editor-test example
env RUST_LOG=editor_test=info target/release/editor-test

144
vendor/cosmic-text/tests/common/mod.rs vendored Normal file
View File

@@ -0,0 +1,144 @@
use std::path::PathBuf;
use cosmic_text::{
fontdb::Database, Attrs, AttrsOwned, Buffer, Color, Family, FontSystem, Metrics, Shaping,
SwashCache,
};
use tiny_skia::{Paint, Pixmap, Rect, Transform};
/// The test configuration.
/// The text in the test will be rendered as image using the one of the fonts found under the
/// `fonts` directory in this repository.
/// The image will then be compared to an image with the name `name` under the `tests/images`
/// directory in this repository.
/// If the images do not match the test will fail.
/// NOTE: if an environment variable `GENERATE_IMAGES` is set, the test will create and save
/// the images instead.
#[derive(Debug)]
pub struct DrawTestCfg {
/// The name of the test.
/// Will be used for the image name under the `tests/images` directory in this repository.
name: String,
/// The text to render to image
text: String,
/// The name, details of the font to be used.
/// Expected to be one of the fonts found under the `fonts` directory in this repository.
font: AttrsOwned,
font_size: f32,
line_height: f32,
canvas_width: u32,
canvas_height: u32,
}
impl Default for DrawTestCfg {
fn default() -> Self {
let font = Attrs::new().family(Family::Serif);
Self {
name: "default".into(),
font: AttrsOwned::new(font),
text: "".into(),
font_size: 16.0,
line_height: 20.0,
canvas_width: 300,
canvas_height: 300,
}
}
}
impl DrawTestCfg {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
pub fn text(mut self, text: impl Into<String>) -> Self {
self.text = text.into();
self
}
pub fn font_attrs(mut self, attrs: Attrs) -> Self {
self.font = AttrsOwned::new(attrs);
self
}
pub fn font_size(mut self, font_size: f32, line_height: f32) -> Self {
self.font_size = font_size;
self.line_height = line_height;
self
}
pub fn canvas(mut self, width: u32, height: u32) -> Self {
self.canvas_width = width;
self.canvas_height = height;
self
}
pub fn validate_text_rendering(self) {
let repo_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
// Create a db with just the fonts in our fonts dir to make sure we only test those
let fonts_path = PathBuf::from(&repo_dir).join("fonts");
let mut font_db = Database::new();
font_db.load_fonts_dir(fonts_path);
let mut font_system = FontSystem::new_with_locale_and_db("En-US".into(), font_db);
let mut swash_cache = SwashCache::new();
let metrics = Metrics::new(self.font_size, self.line_height);
let mut buffer = Buffer::new(&mut font_system, metrics);
let mut buffer = buffer.borrow_with(&mut font_system);
let margins = 5;
buffer.set_size(
Some((self.canvas_width - margins * 2) as f32),
Some((self.canvas_height - margins * 2) as f32),
);
buffer.set_text(&self.text, self.font.as_attrs(), Shaping::Advanced);
buffer.shape_until_scroll(true);
// Black
let text_color = Color::rgb(0x00, 0x00, 0x00);
let mut pixmap = Pixmap::new(self.canvas_width, self.canvas_height).unwrap();
pixmap.fill(tiny_skia::Color::WHITE);
buffer.draw(&mut swash_cache, text_color, |x, y, w, h, color| {
let mut paint = Paint {
anti_alias: true,
..Paint::default()
};
paint.set_color_rgba8(color.r(), color.g(), color.b(), color.a());
let rect = Rect::from_xywh(
(x + margins as i32) as f32,
(y + margins as i32) as f32,
w as f32,
h as f32,
)
.unwrap();
pixmap.fill_rect(rect, &paint, Transform::identity(), None);
});
let image_name = format!("{}.png", self.name);
let reference_image_path = PathBuf::from(&repo_dir)
.join("tests")
.join("images")
.join(image_name);
let generate_images = std::env::var("GENERATE_IMAGES")
.map(|v| {
let val = v.trim().to_ascii_lowercase();
["t", "true", "1"].iter().any(|&v| v == val)
})
.unwrap_or_default();
if generate_images {
pixmap.save_png(reference_image_path).unwrap();
} else {
let reference_image_data = std::fs::read(reference_image_path).unwrap();
let image_data = pixmap.encode_png().unwrap();
assert_eq!(
reference_image_data, image_data,
"rendering failed of {self:?}"
)
}
}
}

View File

@@ -0,0 +1,227 @@
#![cfg(feature = "vi")]
use std::sync::OnceLock;
use cosmic_text::{Buffer, Cursor, Edit, Metrics, SyntaxEditor, SyntaxSystem, ViEditor};
static SYNTAX_SYSTEM: OnceLock<SyntaxSystem> = OnceLock::new();
// New editor for tests
fn editor() -> ViEditor<'static, 'static> {
// More or less copied from cosmic-edit
let font_size: f32 = 14.0;
let line_height = (font_size * 1.4).ceil();
let metrics = Metrics::new(font_size, line_height);
let buffer = Buffer::new_empty(metrics);
let editor = SyntaxEditor::new(
buffer,
SYNTAX_SYSTEM.get_or_init(SyntaxSystem::new),
"base16-eighties.dark",
)
.expect("Default theme `base16-eighties.dark` should be found");
ViEditor::new(editor)
}
// Tests that inserting into an empty editor correctly sets the editor as modified.
#[test]
fn insert_in_empty_editor_sets_changed() {
let mut editor = editor();
assert!(!editor.changed());
editor.start_change();
editor.insert_at(Cursor::new(0, 0), "Robert'); DROP TABLE Students;--", None);
editor.finish_change();
assert!(editor.changed());
}
// Tests an edge case where a save point is never set.
// Undoing changes should set the editor back to unmodified.
#[test]
fn insert_and_undo_in_unsaved_editor_is_unchanged() {
let mut editor = editor();
assert!(!editor.changed());
editor.start_change();
editor.insert_at(Cursor::new(0, 0), "loop {}", None);
editor.finish_change();
assert!(editor.changed());
// Undoing the above change should set the editor as unchanged even if the save state is unset
editor.start_change();
editor.undo();
editor.finish_change();
assert!(!editor.changed());
}
#[test]
fn undo_to_save_point_sets_editor_to_unchanged() {
let mut editor = editor();
// Latest saved change is the first change
editor.start_change();
let cursor = editor.insert_at(Cursor::new(0, 0), "Ferris is Rust's ", None);
editor.finish_change();
assert!(
editor.changed(),
"Editor should be set to changed after insertion"
);
editor.save_point();
assert!(
!editor.changed(),
"Editor should be set to unchanged after setting a save point"
);
// A new insert should set the editor as modified and the pivot should still be on the first
// change from earlier
editor.start_change();
editor.insert_at(cursor, "mascot", None);
editor.finish_change();
assert!(
editor.changed(),
"Editor should be set to changed after inserting text after a save point"
);
// Undoing the latest change should set the editor to unmodified again
editor.start_change();
editor.undo();
editor.finish_change();
assert!(
!editor.changed(),
"Editor should be set to unchanged after undoing to save point"
);
}
#[test]
fn redoing_to_save_point_sets_editor_as_unchanged() {
let mut editor = editor();
// Initial change
assert!(
!editor.changed(),
"Editor should start in an unchanged state"
);
editor.start_change();
editor.insert_at(Cursor::new(0, 0), "editor.start_change();", None);
editor.finish_change();
assert!(
editor.changed(),
"Editor should be set as modified after insert() and finish_change()"
);
editor.save_point();
assert!(
!editor.changed(),
"Editor should be unchanged after setting a save point"
);
// Change to undo then redo
editor.start_change();
editor.insert_at(Cursor::new(1, 0), "editor.finish_change()", None);
editor.finish_change();
assert!(
editor.changed(),
"Editor should be set as modified after insert() and finish_change()"
);
editor.save_point();
assert!(
!editor.changed(),
"Editor should be unchanged after setting a save point"
);
editor.undo();
assert!(
editor.changed(),
"Undoing past save point should set editor as changed"
);
editor.redo();
assert!(
!editor.changed(),
"Redoing to save point should set editor as unchanged"
);
}
#[test]
fn redoing_past_save_point_sets_editor_to_changed() {
let mut editor = editor();
// Save point change to undo to and then redo past.
editor.start_change();
editor.insert_string("Walt Whitman ", None);
editor.finish_change();
// Set save point to the change above.
assert!(
editor.changed(),
"Editor should be set as modified after insert() and finish_change()"
);
editor.save_point();
assert!(
!editor.changed(),
"Editor should be unchanged after setting a save point"
);
editor.start_change();
editor.insert_string("Allen Ginsberg ", None);
editor.finish_change();
editor.start_change();
editor.insert_string("Jack Kerouac ", None);
editor.finish_change();
assert!(editor.changed(), "Editor should be modified insertion");
// Undo to Whitman
editor.undo();
editor.undo();
assert!(
!editor.changed(),
"Editor should be unmodified after undoing to the save point"
);
// Redo to Kerouac
editor.redo();
editor.redo();
assert!(
editor.changed(),
"Editor should be modified after redoing past the save point"
);
}
#[test]
fn undoing_past_save_point_sets_editor_to_changed() {
let mut editor = editor();
editor.start_change();
editor.insert_string("Robert Fripp ", None);
editor.finish_change();
// Save point change to undo past.
editor.start_change();
editor.insert_string("Thurston Moore ", None);
editor.finish_change();
assert!(editor.changed(), "Editor should be changed after insertion");
editor.save_point();
assert!(
!editor.changed(),
"Editor should be unchanged after setting a save point"
);
editor.start_change();
editor.insert_string("Kim Deal ", None);
editor.finish_change();
// Undo to the first change
editor.undo();
editor.undo();
assert!(
editor.changed(),
"Editor should be changed after undoing past save point"
);
}
// #[test]
// fn undo_all_changes() {
// unimplemented!()
// }

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1,75 @@
use common::DrawTestCfg;
use cosmic_text::Attrs;
use fontdb::Family;
mod common;
#[test]
fn test_hebrew_word_rendering() {
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("a_hebrew_word")
.font_size(36., 40.)
.font_attrs(attrs)
.text("בדיקה")
.canvas(120, 60)
.validate_text_rendering();
}
#[test]
fn test_hebrew_paragraph_rendering() {
let paragraph = "השועל החום המהיר קופץ מעל הכלב העצלן";
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("a_hebrew_paragraph")
.font_size(36., 40.)
.font_attrs(attrs)
.text(paragraph)
.canvas(400, 110)
.validate_text_rendering();
}
#[test]
fn test_english_mixed_with_hebrew_paragraph_rendering() {
let paragraph = "Many computer programs fail to display bidirectional text correctly. For example, this page is mostly LTR English script, and here is the RTL Hebrew name Sarah: שרה, spelled sin (ש) on the right, resh (ר) in the middle, and heh (ה) on the left.";
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("some_english_mixed_with_hebrew")
.font_size(16., 20.)
.font_attrs(attrs)
.text(paragraph)
.canvas(400, 120)
.validate_text_rendering();
}
#[test]
fn test_arabic_word_rendering() {
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("an_arabic_word")
.font_size(36., 40.)
.font_attrs(attrs)
.text("خالصة")
.canvas(120, 60)
.validate_text_rendering();
}
#[test]
fn test_arabic_paragraph_rendering() {
let paragraph = "الثعلب البني السريع يقفز فوق الكلب الكسول";
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("an_arabic_paragraph")
.font_size(36., 40.)
.font_attrs(attrs)
.text(paragraph)
.canvas(400, 110)
.validate_text_rendering();
}
#[test]
fn test_english_mixed_with_arabic_paragraph_rendering() {
let paragraph = "I like to render اللغة العربية in Rust!";
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("some_english_mixed_with_arabic")
.font_size(36., 40.)
.font_attrs(attrs)
.text(paragraph)
.canvas(400, 110)
.validate_text_rendering();
}

View File

@@ -0,0 +1,132 @@
use cosmic_text::{
fontdb, Align, Attrs, AttrsList, BidiParagraphs, Buffer, Family, FontSystem, LayoutLine,
Metrics, ShapeLine, Shaping, Weight, Wrap,
};
// Test for https://github.com/pop-os/cosmic-text/issues/134
//
// Being able to get the same wrapping when feeding the measured width back into ShapeLine::layout
// as the new width limit is very useful for certain UI layout use cases.
#[test]
fn stable_wrap() {
let font_size = 18.0;
let attrs = AttrsList::new(
Attrs::new()
.family(Family::Name("FiraMono"))
.weight(Weight::MEDIUM),
);
let mut font_system =
FontSystem::new_with_locale_and_db("en-US".into(), fontdb::Database::new());
let font = std::fs::read("fonts/FiraMono-Medium.ttf").unwrap();
font_system.db_mut().load_font_data(font);
let mut check_wrap = |text: &_, wrap, align_opt, start_width_opt| {
let line = ShapeLine::new(&mut font_system, text, &attrs, Shaping::Advanced, 8);
let layout_unbounded = line.layout(font_size, start_width_opt, wrap, align_opt, None);
let max_width = layout_unbounded.iter().map(|l| l.w).fold(0.0, f32::max);
let new_limit = match start_width_opt {
Some(start_width) => f32::min(start_width, max_width),
None => max_width,
};
let layout_bounded = line.layout(font_size, Some(new_limit), wrap, align_opt, None);
let bounded_max_width = layout_bounded.iter().map(|l| l.w).fold(0.0, f32::max);
// For debugging:
// dbg_layout_lines(text, &layout_unbounded);
// dbg_layout_lines(text, &layout_bounded);
assert_eq!(
(max_width, layout_unbounded.len()),
(bounded_max_width, layout_bounded.len()),
"Wrap \"{wrap:?}\" and align \"{align_opt:?}\" with text: \"{text}\"",
);
for (u, b) in layout_unbounded[1..].iter().zip(layout_bounded[1..].iter()) {
assert_eq!(
u.w, b.w,
"Wrap {wrap:?} and align \"{align_opt:?}\" with text: \"{text}\"",
);
}
};
let hello_sample = std::fs::read_to_string("sample/hello.txt").unwrap();
let cases = [
"(6) SomewhatBoringDisplayTransform",
"",
" ",
" ",
" ",
" ",
]
.into_iter()
// This has several cases where the line would wrap when the computed width was used as the
// width limit.
.chain(BidiParagraphs::new(&hello_sample));
for text in cases {
for wrap in [Wrap::None, Wrap::Glyph, Wrap::Word, Wrap::WordOrGlyph] {
for align_opt in [
None,
Some(Align::Left),
Some(Align::Right),
Some(Align::Center),
//TODO: Align::Justified
Some(Align::End),
] {
for start_width_opt in [
None,
Some(f32::MAX),
Some(80.0),
Some(198.2132),
Some(20.0),
Some(4.0),
Some(300.0),
] {
check_wrap(text, wrap, align_opt, start_width_opt);
let with_spaces = format!("{text} ");
check_wrap(&with_spaces, wrap, align_opt, start_width_opt);
let with_spaces_2 = format!("{text} ");
check_wrap(&with_spaces_2, wrap, align_opt, start_width_opt);
}
}
}
}
}
#[test]
fn wrap_extra_line() {
let mut font_system = FontSystem::new();
let metrics = Metrics::new(14.0, 20.0);
let mut buffer = Buffer::new(&mut font_system, metrics);
let mut buffer = buffer.borrow_with(&mut font_system);
// Add some text!
buffer.set_wrap(Wrap::Word);
buffer.set_text("Lorem ipsum dolor sit amet, qui minim labore adipisicing\n\nweeewoooo minim sint cillum sint consectetur cupidatat.", Attrs::new().family(cosmic_text::Family::Name("Inter")), Shaping::Advanced);
// Set a size for the text buffer, in pixels
buffer.set_size(Some(50.0), Some(1000.0));
// Perform shaping as desired
buffer.shape_until_scroll(false);
let empty_lines = buffer.layout_runs().filter(|x| x.line_w == 0.).count();
let overflow_lines = buffer.layout_runs().filter(|x| x.line_w > 50.).count();
assert_eq!(empty_lines, 1);
assert_eq!(overflow_lines, 4);
}
#[allow(dead_code)]
fn dbg_layout_lines(text: &str, lines: &[LayoutLine]) {
for line in lines {
let mut s = String::new();
for glyph in line.glyphs.iter() {
s.push_str(&text[glyph.start..glyph.end]);
}
println!("\"{s}\"");
}
}

View File

@@ -0,0 +1,37 @@
use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, Wrap};
// Tests the ability to fallback to glyph wrapping when a word can't fit on a line by itself.
// No line should ever overflow the buffer size.
#[test]
fn wrap_word_fallback() {
let mut font_system =
FontSystem::new_with_locale_and_db("en-US".into(), fontdb::Database::new());
let font = std::fs::read("fonts/Inter-Regular.ttf").unwrap();
font_system.db_mut().load_font_data(font);
let metrics = Metrics::new(14.0, 20.0);
let mut buffer = Buffer::new(&mut font_system, metrics);
let mut buffer = buffer.borrow_with(&mut font_system);
buffer.set_wrap(Wrap::WordOrGlyph);
buffer.set_text("Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.", Attrs::new().family(cosmic_text::Family::Name("Inter")), Shaping::Advanced);
buffer.set_size(Some(50.0), Some(1000.0));
buffer.shape_until_scroll(false);
let measured_size = measure(&buffer);
assert!(
measured_size <= buffer.size().0.unwrap_or(0.0),
"Measured width is larger than buffer width\n{} <= {}",
measured_size,
buffer.size().0.unwrap_or(0.0)
);
}
fn measure(buffer: &Buffer) -> f32 {
buffer
.layout_runs()
.fold(0.0f32, |width, run| width.max(run.line_w))
}