Text library moved from libcosmic
This commit is contained in:
commit
410d4ee674
37 changed files with 12909 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Cargo.lock
|
||||
target
|
||||
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "cosmic-text"
|
||||
version = "0.1.0"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2021"
|
||||
license = "MPL2"
|
||||
|
||||
[dependencies]
|
||||
fontdb = "0.9"
|
||||
log = "0.4"
|
||||
rustybuzz = "0.5"
|
||||
swash = "0.1"
|
||||
sys-locale = "0.2"
|
||||
unicode-bidi = "0.3"
|
||||
unicode-linebreak = "0.1"
|
||||
unicode-script = "0.5"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"examples/editor-libcosmic",
|
||||
"examples/editor-orbclient",
|
||||
]
|
||||
373
LICENSE
Normal file
373
LICENSE
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
14
README.md
Normal file
14
README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# COSMIC Text
|
||||
An example showcasing multi-line text shaping and rendering.
|
||||
|
||||
All the example code is located in the __[`main`](src/main.rs)__ file.
|
||||
|
||||
You can run it with `cargo run`:
|
||||
```
|
||||
cargo run --package text --release
|
||||
```
|
||||
|
||||
To test with a monospace font, you can run it with the `mono` feature:
|
||||
```
|
||||
cargo run --package text --features mono --release
|
||||
```
|
||||
1
editor-libcosmic.sh
Executable file
1
editor-libcosmic.sh
Executable file
|
|
@ -0,0 +1 @@
|
|||
RUST_LOG="cosmic_text=debug,editor_libcosmic=debug" cargo run --release --package editor-libcosmic -- "$@"
|
||||
1
editor-orbclient.sh
Executable file
1
editor-orbclient.sh
Executable file
|
|
@ -0,0 +1 @@
|
|||
RUST_LOG="cosmic_text=debug,editor_orbclient=debug" cargo run --release --package editor-orbclient -- "$@"
|
||||
22
examples/editor-libcosmic/Cargo.toml
Normal file
22
examples/editor-libcosmic/Cargo.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "editor-libcosmic"
|
||||
version = "0.1.0"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2021"
|
||||
license = "MPL2"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
cosmic-text = { path = "../../" }
|
||||
env_logger = "0.9"
|
||||
fontdb = "0.9"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
|
||||
[dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
branch = "cosmic-design-system"
|
||||
#path = "../../../libcosmic"
|
||||
|
||||
[features]
|
||||
mono = []
|
||||
139
examples/editor-libcosmic/src/main.rs
Normal file
139
examples/editor-libcosmic/src/main.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
use cosmic::{
|
||||
iced::widget::{
|
||||
container,
|
||||
},
|
||||
iced::{
|
||||
self,
|
||||
Application,
|
||||
Command,
|
||||
Element,
|
||||
Theme
|
||||
},
|
||||
settings,
|
||||
};
|
||||
use cosmic_text::{
|
||||
FontMatches,
|
||||
FontSystem,
|
||||
TextBuffer,
|
||||
};
|
||||
use std::{
|
||||
env,
|
||||
fs,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use self::text_box::text_box;
|
||||
mod text_box;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref FONT_SYSTEM: FontSystem = FontSystem::new();
|
||||
}
|
||||
|
||||
//TODO: find out how to do this!
|
||||
static mut FONT_MATCHES: Option<FontMatches<'static>> = None;
|
||||
|
||||
fn main() -> cosmic::iced::Result {
|
||||
env_logger::init();
|
||||
|
||||
let font_matches: FontMatches<'static> = FONT_SYSTEM.matches(|info| -> bool {
|
||||
#[cfg(feature = "mono")]
|
||||
let monospaced = true;
|
||||
|
||||
#[cfg(not(feature = "mono"))]
|
||||
let monospaced = false;
|
||||
|
||||
let matched = {
|
||||
info.style == fontdb::Style::Normal &&
|
||||
info.weight == fontdb::Weight::NORMAL &&
|
||||
info.stretch == fontdb::Stretch::Normal &&
|
||||
(info.monospaced == monospaced || info.post_script_name.contains("Emoji"))
|
||||
};
|
||||
|
||||
if matched {
|
||||
log::debug!(
|
||||
"{:?}: family '{}' postscript name '{}' style {:?} weight {:?} stretch {:?} monospaced {:?}",
|
||||
info.id,
|
||||
info.family,
|
||||
info.post_script_name,
|
||||
info.style,
|
||||
info.weight,
|
||||
info.stretch,
|
||||
info.monospaced
|
||||
);
|
||||
}
|
||||
|
||||
matched
|
||||
}).unwrap();
|
||||
|
||||
unsafe { FONT_MATCHES = Some(font_matches); }
|
||||
|
||||
let mut settings = settings();
|
||||
settings.window.min_size = Some((400, 100));
|
||||
Window::run(settings)
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
buffer: Arc<RwLock<TextBuffer<'static>>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Message {}
|
||||
|
||||
impl Application for Window {
|
||||
type Executor = iced::executor::Default;
|
||||
type Flags = ();
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
|
||||
let font_sizes = [
|
||||
(10, 14), // Caption
|
||||
(14, 20), // Body
|
||||
(20, 28), // Title 4
|
||||
(24, 32), // Title 3
|
||||
(28, 36), // Title 2
|
||||
(32, 44), // Title 1
|
||||
];
|
||||
let font_size_default = 1; // Body
|
||||
let mut font_size_i = font_size_default;
|
||||
|
||||
let text = if let Some(arg) = env::args().nth(1) {
|
||||
fs::read_to_string(&arg).expect("failed to open file")
|
||||
} else {
|
||||
#[cfg(feature = "mono")]
|
||||
let default_text = include_str!("../../../sample/mono.txt");
|
||||
#[cfg(not(feature = "mono"))]
|
||||
let default_text = include_str!("../../../sample/proportional.txt");
|
||||
default_text.to_string()
|
||||
};
|
||||
|
||||
let line_x = 8;
|
||||
let buffer = Arc::new(RwLock::new(TextBuffer::new(
|
||||
unsafe { FONT_MATCHES.as_ref().unwrap() },
|
||||
&text,
|
||||
font_sizes[font_size_i].0,
|
||||
600 - line_x * 2,
|
||||
)));
|
||||
|
||||
let window = Window {
|
||||
buffer,
|
||||
};
|
||||
(window, Command::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
let buffer = self.buffer.read().unwrap();
|
||||
format!("COSMIC Text - iced - {}", buffer.font_matches().locale)
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
container(
|
||||
text_box(self.buffer.clone())
|
||||
).padding(16).into()
|
||||
}
|
||||
}
|
||||
272
examples/editor-libcosmic/src/text_box.rs
Normal file
272
examples/editor-libcosmic/src/text_box.rs
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
use cosmic::iced_native::{
|
||||
{Color, Element, Length, Point, Rectangle, Size, Shell},
|
||||
clipboard::Clipboard,
|
||||
event::{
|
||||
Event,
|
||||
Status,
|
||||
},
|
||||
keyboard::{Event as KeyEvent, KeyCode},
|
||||
layout::{self, Layout},
|
||||
renderer,
|
||||
widget::{self, Widget},
|
||||
};
|
||||
use cosmic_text::{
|
||||
FontLineIndex,
|
||||
TextAction,
|
||||
TextBuffer,
|
||||
};
|
||||
use std::{
|
||||
sync::{Arc, RwLock},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
pub struct TextBox<'a> {
|
||||
buffer: Arc<RwLock<TextBuffer<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> TextBox<'a> {
|
||||
pub fn new(buffer: Arc<RwLock<TextBuffer<'a>>>) -> Self {
|
||||
Self { buffer }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_box<'a>(buffer: Arc<RwLock<TextBuffer<'a>>>) -> TextBox<'a> {
|
||||
TextBox::new(buffer)
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for TextBox<'a>
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
println!("{:?}", limits);
|
||||
let size = limits.max();
|
||||
{
|
||||
let mut buffer = self.buffer.write().unwrap();
|
||||
|
||||
let font_size = buffer.font_size();
|
||||
let line_height = font_size + 8;
|
||||
|
||||
buffer.set_line_width(size.width as i32);
|
||||
buffer.shape_until(size.height as i32 / line_height);
|
||||
}
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &widget::Tree,
|
||||
renderer: &mut Renderer,
|
||||
_theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let buffer = self.buffer.read().unwrap();
|
||||
let font_size = buffer.font_size();
|
||||
let line_height = font_size + 8; /*TODO: store somewhere else */
|
||||
let scroll = 0;
|
||||
|
||||
let instant = Instant::now();
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: layout.bounds(),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Color::from_rgb8(0x34, 0x34, 0x34),
|
||||
);
|
||||
|
||||
let line_x = layout.bounds().x as i32;
|
||||
let mut line_y = layout.bounds().y as i32 + line_height;
|
||||
let mut start_line_opt = None;
|
||||
let mut end_line = FontLineIndex::new(0);
|
||||
for (line_i, line) in buffer
|
||||
.layout_lines()
|
||||
.iter()
|
||||
.skip(scroll as usize)
|
||||
.enumerate()
|
||||
{
|
||||
if line_y >= (layout.bounds().y + layout.bounds().height) as i32 {
|
||||
break;
|
||||
}
|
||||
|
||||
end_line = line.line_i;
|
||||
if start_line_opt == None {
|
||||
start_line_opt = Some(end_line);
|
||||
}
|
||||
|
||||
if buffer.cursor.line == line_i + scroll as usize {
|
||||
if buffer.cursor.glyph >= line.glyphs.len() {
|
||||
let x = match line.glyphs.last() {
|
||||
Some(glyph) => glyph.x + glyph.w,
|
||||
None => 0.0,
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle::new(
|
||||
Point::new(line_x as f32 + x, (line_y - font_size) as f32),
|
||||
Size::new((font_size / 2) as f32, line_height as f32)
|
||||
),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.125),
|
||||
);
|
||||
} else {
|
||||
let glyph = &line.glyphs[buffer.cursor.glyph];
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle::new(
|
||||
Point::new(line_x as f32 + glyph.x, (line_y - font_size) as f32),
|
||||
Size::new(glyph.w, line_height as f32)
|
||||
),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.125),
|
||||
);
|
||||
|
||||
let text_line = &buffer.text_lines()[line.line_i.get()];
|
||||
log::info!(
|
||||
"{}, {}: '{}' ('{}'): '{}'",
|
||||
glyph.start,
|
||||
glyph.end,
|
||||
glyph.font.info.family,
|
||||
glyph.font.info.post_script_name,
|
||||
&text_line[glyph.start..glyph.end],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
line.draw(0xFFFFFF, |x, y, data| {
|
||||
let a = (data >> 24) as u8;
|
||||
if a > 0 {
|
||||
let r = (data >> 16) as u8;
|
||||
let g = (data >> 8) as u8;
|
||||
let b = data as u8;
|
||||
let bounds = Rectangle::new(
|
||||
Point::new((line_x + x) as f32, (line_y + y) as f32),
|
||||
Size::new(1.0, 1.0)
|
||||
);
|
||||
let color = Color::from_rgba8(r, g, b, a as f32 / 255.0);
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
color
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
line_y += line_height;
|
||||
}
|
||||
|
||||
/*
|
||||
// Draw scrollbar
|
||||
{
|
||||
let start_line = start_line_opt.unwrap_or(end_line);
|
||||
let lines = buffer.text_lines().len();
|
||||
let start_y = (start_line.get() * window.height() as usize) / lines;
|
||||
let end_y = (end_line.get() * window.height() as usize) / lines;
|
||||
if end_y > start_y {
|
||||
window.rect(
|
||||
window.width() as i32 - line_x as i32,
|
||||
start_y as i32,
|
||||
line_x as u32,
|
||||
(end_y - start_y) as u32,
|
||||
Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.25),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
buffer.redraw = false;
|
||||
*/
|
||||
|
||||
let duration = instant.elapsed();
|
||||
log::debug!("redraw: {:?}", duration);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_state: &mut widget::Tree,
|
||||
event: Event,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
_shell: &mut Shell<'_, Message>,
|
||||
) -> Status {
|
||||
let mut buffer = self.buffer.write().unwrap();
|
||||
|
||||
match event {
|
||||
Event::Keyboard(key_event) => match key_event {
|
||||
KeyEvent::KeyPressed { key_code, modifiers } => {
|
||||
match key_code {
|
||||
KeyCode::Left => {
|
||||
buffer.action(TextAction::Left);
|
||||
Status::Captured
|
||||
},
|
||||
KeyCode::Right => {
|
||||
buffer.action(TextAction::Right);
|
||||
Status::Captured
|
||||
},
|
||||
KeyCode::Up => {
|
||||
buffer.action(TextAction::Up);
|
||||
Status::Captured
|
||||
},
|
||||
KeyCode::Down => {
|
||||
buffer.action(TextAction::Down);
|
||||
Status::Captured
|
||||
},
|
||||
KeyCode::Backspace => {
|
||||
buffer.action(TextAction::Backspace);
|
||||
Status::Captured
|
||||
},
|
||||
KeyCode::Delete => {
|
||||
buffer.action(TextAction::Delete);
|
||||
Status::Captured
|
||||
},
|
||||
_ => Status::Ignored,
|
||||
}
|
||||
},
|
||||
KeyEvent::CharacterReceived(character) => {
|
||||
buffer.action(TextAction::Insert(character));
|
||||
Status::Captured
|
||||
},
|
||||
_ => Status::Ignored,
|
||||
},
|
||||
_ => Status::Ignored,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<TextBox<'a>> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn from(text_box: TextBox<'a>) -> Self {
|
||||
Self::new(text_box)
|
||||
}
|
||||
}
|
||||
17
examples/editor-orbclient/Cargo.toml
Normal file
17
examples/editor-orbclient/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "editor-orbclient"
|
||||
version = "0.1.0"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2021"
|
||||
license = "MPL2"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
cosmic-text = { path = "../../" }
|
||||
env_logger = "0.9"
|
||||
fontdb = "0.9"
|
||||
log = "0.4"
|
||||
orbclient = "0.3.35"
|
||||
|
||||
[features]
|
||||
mono = []
|
||||
328
examples/editor-orbclient/src/main.rs
Normal file
328
examples/editor-orbclient/src/main.rs
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
use cosmic_text::{FontLineIndex, FontSystem, TextAction, TextBuffer, TextCursor};
|
||||
use orbclient::{Color, EventOption, Renderer, Window, WindowFlag};
|
||||
use std::{cmp, env, fs, time::Instant};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let display_scale = match orbclient::get_display_size() {
|
||||
Ok((w, h)) => {
|
||||
log::info!("Display size: {}, {}", w, h);
|
||||
(h as i32 / 1600) + 1
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("Failed to get display size: {}", err);
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
let font_system = FontSystem::new();
|
||||
|
||||
let mut window = Window::new_flags(
|
||||
-1,
|
||||
-1,
|
||||
1024 * display_scale as u32,
|
||||
768 * display_scale as u32,
|
||||
&format!("COSMIC TEXT - {}", font_system.locale),
|
||||
&[WindowFlag::Resizable],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let font_matches = font_system.matches(|info| -> bool {
|
||||
#[cfg(feature = "mono")]
|
||||
let monospaced = true;
|
||||
|
||||
#[cfg(not(feature = "mono"))]
|
||||
let monospaced = false;
|
||||
|
||||
let matched = {
|
||||
info.style == fontdb::Style::Normal &&
|
||||
info.weight == fontdb::Weight::NORMAL &&
|
||||
info.stretch == fontdb::Stretch::Normal &&
|
||||
(info.monospaced == monospaced || info.post_script_name.contains("Emoji"))
|
||||
};
|
||||
|
||||
if matched {
|
||||
log::debug!(
|
||||
"{:?}: family '{}' postscript name '{}' style {:?} weight {:?} stretch {:?} monospaced {:?}",
|
||||
info.id,
|
||||
info.family,
|
||||
info.post_script_name,
|
||||
info.style,
|
||||
info.weight,
|
||||
info.stretch,
|
||||
info.monospaced
|
||||
);
|
||||
}
|
||||
|
||||
matched
|
||||
}).unwrap();
|
||||
|
||||
let bg_color = Color::rgb(0x34, 0x34, 0x34);
|
||||
let font_color = Color::rgb(0xFF, 0xFF, 0xFF);
|
||||
let font_sizes = [
|
||||
(10, 14), // Caption
|
||||
(14, 20), // Body
|
||||
(20, 28), // Title 4
|
||||
(24, 32), // Title 3
|
||||
(28, 36), // Title 2
|
||||
(32, 44), // Title 1
|
||||
];
|
||||
let font_size_default = 1; // Body
|
||||
let mut font_size_i = font_size_default;
|
||||
|
||||
let text = if let Some(arg) = env::args().nth(1) {
|
||||
fs::read_to_string(&arg).expect("failed to open file")
|
||||
} else {
|
||||
#[cfg(feature = "mono")]
|
||||
let default_text = include_str!("../../../sample/mono.txt");
|
||||
#[cfg(not(feature = "mono"))]
|
||||
let default_text = include_str!("../../../sample/proportional.txt");
|
||||
default_text.to_string()
|
||||
};
|
||||
|
||||
let line_x = 8 * display_scale;
|
||||
let mut buffer = TextBuffer::new(
|
||||
&font_matches,
|
||||
&text,
|
||||
font_sizes[font_size_i].0 * display_scale,
|
||||
window.width() as i32 - line_x * 2,
|
||||
);
|
||||
|
||||
let mut ctrl_pressed = false;
|
||||
let mut mouse_x = -1;
|
||||
let mut mouse_y = -1;
|
||||
let mut mouse_left = false;
|
||||
let mut rehit = false;
|
||||
let mut scroll = 0;
|
||||
loop {
|
||||
let font_size = buffer.font_size();
|
||||
let line_height = font_sizes[font_size_i].1 * display_scale;
|
||||
|
||||
let window_lines = (window.height() as i32 + line_height - 1) / line_height;
|
||||
|
||||
buffer.shape_until(scroll + window_lines);
|
||||
|
||||
scroll = cmp::max(
|
||||
0,
|
||||
cmp::min(
|
||||
buffer.layout_lines().len() as i32 - (window_lines - 1),
|
||||
scroll,
|
||||
),
|
||||
);
|
||||
|
||||
if rehit {
|
||||
let instant = Instant::now();
|
||||
|
||||
let mut new_cursor_opt = None;
|
||||
|
||||
let mut line_y = line_height;
|
||||
for (line_i, line) in buffer
|
||||
.layout_lines()
|
||||
.iter()
|
||||
.skip(scroll as usize)
|
||||
.enumerate()
|
||||
{
|
||||
if line_y >= window.height() as i32 {
|
||||
break;
|
||||
}
|
||||
|
||||
if mouse_left
|
||||
&& mouse_y >= line_y - font_size
|
||||
&& mouse_y < line_y - font_size + line_height
|
||||
{
|
||||
let new_cursor_line = line_i + scroll as usize;
|
||||
let mut new_cursor_glyph = line.glyphs.len();
|
||||
for (glyph_i, glyph) in line.glyphs.iter().enumerate() {
|
||||
if mouse_x >= line_x + glyph.x as i32
|
||||
&& mouse_x <= line_x + (glyph.x + glyph.w) as i32
|
||||
{
|
||||
new_cursor_glyph = glyph_i;
|
||||
}
|
||||
}
|
||||
new_cursor_opt = Some(TextCursor::new(new_cursor_line, new_cursor_glyph));
|
||||
}
|
||||
|
||||
line_y += line_height;
|
||||
}
|
||||
|
||||
if let Some(new_cursor) = new_cursor_opt {
|
||||
if new_cursor != buffer.cursor {
|
||||
buffer.cursor = new_cursor;
|
||||
buffer.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
rehit = false;
|
||||
|
||||
let duration = instant.elapsed();
|
||||
log::debug!("rehit: {:?}", duration);
|
||||
}
|
||||
|
||||
if buffer.redraw {
|
||||
let instant = Instant::now();
|
||||
|
||||
window.set(bg_color);
|
||||
|
||||
let mut line_y = line_height;
|
||||
let mut start_line_opt = None;
|
||||
let mut end_line = FontLineIndex::new(0);
|
||||
for (line_i, line) in buffer
|
||||
.layout_lines()
|
||||
.iter()
|
||||
.skip(scroll as usize)
|
||||
.enumerate()
|
||||
{
|
||||
if line_y >= window.height() as i32 {
|
||||
break;
|
||||
}
|
||||
|
||||
end_line = line.line_i;
|
||||
if start_line_opt == None {
|
||||
start_line_opt = Some(end_line);
|
||||
}
|
||||
|
||||
if buffer.cursor.line == line_i + scroll as usize {
|
||||
if buffer.cursor.glyph >= line.glyphs.len() {
|
||||
let x = match line.glyphs.last() {
|
||||
Some(glyph) => glyph.x + glyph.w,
|
||||
None => 0.0,
|
||||
};
|
||||
window.rect(
|
||||
line_x + x as i32,
|
||||
line_y - font_size,
|
||||
(font_size / 2) as u32,
|
||||
line_height as u32,
|
||||
Color::rgba(0xFF, 0xFF, 0xFF, 0x20),
|
||||
);
|
||||
} else {
|
||||
let glyph = &line.glyphs[buffer.cursor.glyph];
|
||||
window.rect(
|
||||
line_x + glyph.x as i32,
|
||||
line_y - font_size,
|
||||
glyph.w as u32,
|
||||
line_height as u32,
|
||||
Color::rgba(0xFF, 0xFF, 0xFF, 0x20),
|
||||
);
|
||||
|
||||
let text_line = &buffer.text_lines()[line.line_i.get()];
|
||||
log::info!(
|
||||
"{}, {}: '{}' ('{}'): '{}'",
|
||||
glyph.start,
|
||||
glyph.end,
|
||||
glyph.font.info.family,
|
||||
glyph.font.info.post_script_name,
|
||||
&text_line[glyph.start..glyph.end],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
line.draw(font_color.data, |x, y, color| {
|
||||
window.pixel(line_x + x, line_y + y, Color { data: color });
|
||||
});
|
||||
|
||||
line_y += line_height;
|
||||
}
|
||||
|
||||
// Draw scrollbar
|
||||
{
|
||||
let start_line = start_line_opt.unwrap_or(end_line);
|
||||
let lines = buffer.text_lines().len();
|
||||
let start_y = (start_line.get() * window.height() as usize) / lines;
|
||||
let end_y = (end_line.get() * window.height() as usize) / lines;
|
||||
if end_y > start_y {
|
||||
window.rect(
|
||||
window.width() as i32 - line_x as i32,
|
||||
start_y as i32,
|
||||
line_x as u32,
|
||||
(end_y - start_y) as u32,
|
||||
Color::rgba(0xFF, 0xFF, 0xFF, 0x40),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.sync();
|
||||
|
||||
buffer.redraw = false;
|
||||
|
||||
let duration = instant.elapsed();
|
||||
log::debug!("redraw: {:?}", duration);
|
||||
}
|
||||
|
||||
for event in window.events() {
|
||||
match event.to_option() {
|
||||
EventOption::Key(event) => match event.scancode {
|
||||
orbclient::K_CTRL => ctrl_pressed = event.pressed,
|
||||
orbclient::K_LEFT if event.pressed => buffer.action(TextAction::Left),
|
||||
orbclient::K_RIGHT if event.pressed => buffer.action(TextAction::Right),
|
||||
orbclient::K_UP if event.pressed => buffer.action(TextAction::Up),
|
||||
orbclient::K_DOWN if event.pressed => buffer.action(TextAction::Down),
|
||||
orbclient::K_BKSP if event.pressed => buffer.action(TextAction::Backspace),
|
||||
orbclient::K_DEL if event.pressed => buffer.action(TextAction::Delete),
|
||||
orbclient::K_PGUP if event.pressed => {
|
||||
scroll -= window_lines;
|
||||
buffer.redraw = true;
|
||||
},
|
||||
orbclient::K_PGDN if event.pressed => {
|
||||
scroll += window_lines;
|
||||
buffer.redraw = true;
|
||||
},
|
||||
orbclient::K_0 if event.pressed && ctrl_pressed => {
|
||||
font_size_i = font_size_default;
|
||||
buffer.set_font_size(font_sizes[font_size_i].0 * display_scale);
|
||||
},
|
||||
orbclient::K_MINUS if event.pressed && ctrl_pressed => {
|
||||
if font_size_i > 0 {
|
||||
font_size_i -= 1;
|
||||
buffer.set_font_size(font_sizes[font_size_i].0 * display_scale);
|
||||
}
|
||||
},
|
||||
orbclient::K_EQUALS if event.pressed && ctrl_pressed => {
|
||||
if font_size_i + 1 < font_sizes.len() {
|
||||
font_size_i += 1;
|
||||
buffer.set_font_size(font_sizes[font_size_i].0 * display_scale);
|
||||
}
|
||||
},
|
||||
orbclient::K_D if event.pressed && ctrl_pressed => {
|
||||
// Debug by shaping whole buffer
|
||||
log::info!("Shaping rest of buffer");
|
||||
let instant = Instant::now();
|
||||
|
||||
buffer.shape_until(i32::max_value());
|
||||
|
||||
let elapsed = instant.elapsed();
|
||||
log::info!("Shaped rest of buffer in {:?}", elapsed);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
EventOption::TextInput(event) if !ctrl_pressed => {
|
||||
buffer.action(TextAction::Insert(event.character));
|
||||
}
|
||||
EventOption::Mouse(event) => {
|
||||
mouse_x = event.x;
|
||||
mouse_y = event.y;
|
||||
if mouse_left {
|
||||
rehit = true;
|
||||
}
|
||||
}
|
||||
EventOption::Button(event) => {
|
||||
if event.left != mouse_left {
|
||||
mouse_left = event.left;
|
||||
if mouse_left {
|
||||
rehit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
EventOption::Resize(event) => {
|
||||
buffer.set_line_width(event.width as i32 - line_x * 2);
|
||||
}
|
||||
EventOption::Scroll(event) => {
|
||||
scroll -= event.y * 3;
|
||||
buffer.redraw = true;
|
||||
}
|
||||
EventOption::Quit(_) => return,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
sample/arabic.txt
Normal file
3
sample/arabic.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
I like to render اللغة العربية in Rust!
|
||||
|
||||
عندما يريد العالم أن يتكلّم ، فهو يتحدّث بلغة يونيكود. تسجّل الآن لحضور المؤتمر الدولي العاشر ليونيكود (Unicode Conference)، الذي سيعقد في 10-12 آذار 1997 بمدينة مَايِنْتْس، ألمانيا. و سيجمع المؤتمر بين خبراء من كافة قطاعات الصناعة على الشبكة العالمية انترنيت ويونيكود، حيث ستتم، على الصعيدين الدولي والمحلي على حد سواء مناقشة سبل استخدام يونكود في النظم القائمة وفيما يخص التطبيقات الحاسوبية، الخطوط، تصميم النصوص والحوسبة متعددة اللغات.
|
||||
1
sample/bengali.txt
Normal file
1
sample/bengali.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
ধারা ১ সমস্ত মানুষ স্বাধীনভাবে সমান মর্যাদা এবং অধিকার নিয়ে জন্মগ্রহণ করে। তাঁদের বিবেক এবং বুদ্ধি আছে; সুতরাং সকলেরই একে অপরের প্রতি ভ্রাতৃত্বসুলভ মনোভাব নিয়ে আচরণ করা উচিত।
|
||||
1
sample/emoji-zjw.txt
Normal file
1
sample/emoji-zjw.txt
Normal 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
sample/emoji.txt
Normal file
3793
sample/emoji.txt
Normal file
File diff suppressed because it is too large
Load diff
2
sample/fallback.txt
Normal file
2
sample/fallback.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
TE🤯STالعربيةTE🤣ST你好
|
||||
العربيةTE🤯STالعربيةTE🤣ST你好العربية
|
||||
10
sample/farsi.txt
Normal file
10
sample/farsi.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
من از سیستم عامل پاپ! ۲۲.۰۴ استفاده میکنم.
|
||||
متن راست به چپ Left to right text ادامهی متن راست به چپ با اعداد ۱/۲۳۴ و English numbers 1.234.
|
||||
مَتنِ فارسی و انگلیسی (English).
|
||||
کشیده نویسی (کشــــــــــــیدهنویسی).
|
||||
علائم تُ تِ تَ تً تٍ تْ تٌ.
|
||||
لام.
|
||||
ویرگول (؛).
|
||||
تای تأنیث یا ه دو نقطه (ة).
|
||||
علامت تشدید (ــّـ).
|
||||
|
||||
29
sample/han.txt
Normal file
29
sample/han.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
https://en.wikipedia.org/wiki/Han_unification#Examples_of_language-dependent_glyphs
|
||||
今
|
||||
令
|
||||
免
|
||||
入
|
||||
全
|
||||
关
|
||||
具
|
||||
刃
|
||||
化
|
||||
外
|
||||
情
|
||||
才
|
||||
抵
|
||||
次
|
||||
海
|
||||
画
|
||||
直
|
||||
真
|
||||
示
|
||||
神
|
||||
空
|
||||
者
|
||||
草
|
||||
蔥
|
||||
角
|
||||
道
|
||||
雇
|
||||
骨
|
||||
3
sample/hebrew.txt
Normal file
3
sample/hebrew.txt
Normal 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.
|
||||
11
sample/lioneatingpoet.txt
Normal file
11
sample/lioneatingpoet.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
《施氏食狮史》
|
||||
石室诗士施氏,嗜狮,誓食十狮。
|
||||
氏时时适市视狮。
|
||||
十时,适十狮适市。
|
||||
是时,适施氏适市。
|
||||
氏视是十狮,恃矢势,使是十狮逝世。
|
||||
氏拾是十狮尸,适石室。
|
||||
石室湿,氏使侍拭石室。
|
||||
石室拭,氏始试食是十狮。
|
||||
食时,始识是十狮尸,实十石狮尸。
|
||||
试释是事。
|
||||
213
sample/mono.txt
Normal file
213
sample/mono.txt
Normal 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:
|
||||
|
||||
((V⍳V)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈
|
||||
|
||||
Nicer typography in plain text files:
|
||||
|
||||
╔══════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ • ‘single’ and “double” quotes ║
|
||||
║ ║
|
||||
║ • Curly apostrophes: “We’ve been here” ║
|
||||
║ ║
|
||||
║ • Latin-1 apostrophe and accents: '´` ║
|
||||
║ ║
|
||||
║ • ‚deutsche‘ „Anführungszeichen“ ║
|
||||
║ ║
|
||||
║ • †, ‡, ‰, •, 3–4, —, −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: █
|
||||
▉
|
||||
╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
|
||||
║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
|
||||
║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
|
||||
╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
|
||||
║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
|
||||
║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
|
||||
╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
|
||||
▝▀▘▙▄▟
|
||||
218
sample/proportional.txt
Normal file
218
sample/proportional.txt
Normal 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
|
||||
℀ ℁ ℂ ℃ ℄ ℅ ℆ ℇ ℈ ℉ ℊ ℋ ℌ ℍ ℎ ℏ ℐ ℑ ℒ ℓ ℔ ℕ № ℗ ℘ ℙ ℚ ℛ ℜ ℝ ℞ ℟ ℠ ℡ ™ ℣ ℤ ℥ Ω ℧ ℨ ℩ K Å ℬ ℭ ℮ ℯ ℰ ℱ Ⅎ ℳ ℴ ℵ ℶ ℷ ℸ ℹ ℺ ℽ ℾ ℿ ⅀ ⅁ ⅂ ⅃ ⅄ ⅅ ⅆ ⅇ ⅈ ⅉ ⅊ ⅋
|
||||
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
|
||||
! " # $ % & ' ( ) * + , - . / 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 { | } ~ ⦅ ⦆ 。 「 」 、 ・ ヲ ァ ィ ゥ ェ ォ ャ ュ ョ ッ ー ア イ ウ エ オ カ キ ク ケ コ サ シ ス セ ソ タ ...
|
||||
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
|
||||
...
|
||||
3
sample/thai.txt
Normal file
3
sample/thai.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ข้อ 1 มนุษย์ทั้งหลายเกิดมามีอิสระและเสมอภาคกันในเกียรติศักด[เกียรติศักดิ์]และสิทธิ ต่างมีเหตุผลและมโนธรรม และควรปฏิบัติต่อกันด้วยเจตนารมณ์แห่งภราดรภาพ
|
||||
|
||||
ข้อ 2 ทุกคนย่อมมีสิทธิและอิสรภาพบรรดาที่กำหนดไว้ในปฏิญญานี้ โดยปราศจากความแตกต่างไม่ว่าชนิดใด ๆ ดังเช่น เชื้อชาติ ผิว เพศ ภาษา ศาสนา ความคิดเห็นทางการเมืองหรือทางอื่น เผ่าพันธุ์แห่งชาติ หรือสังคม ทรัพย์สิน กำเนิด หรือสถานะอื่น ๆ อนึ่งจะไม่มีความแตกต่างใด ๆ ตามมูลฐานแห่งสถานะทางการเมือง ทางการศาล หรือทางการระหว่างประเทศของประเทศหรือดินแดนที่บุคคลสังกัด ไม่ว่าดินแดนนี้จะเป็นเอกราช อยู่ในความพิทักษ์มิได้ปกครองตนเอง หรืออยู่ภายใต้การจำกัดอธิปไตยใด ๆ ทั้งสิ้น
|
||||
5850
sample/udhr.txt
Normal file
5850
sample/udhr.txt
Normal file
File diff suppressed because it is too large
Load diff
261
src/buffer.rs
Normal file
261
src/buffer.rs
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use crate::{FontLayoutLine, FontLineIndex, FontMatches, FontShapeLine};
|
||||
|
||||
pub enum TextAction {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
Backspace,
|
||||
Delete,
|
||||
Insert(char),
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq)]
|
||||
pub struct TextCursor {
|
||||
pub line: usize,
|
||||
pub glyph: usize,
|
||||
}
|
||||
|
||||
impl TextCursor {
|
||||
pub fn new(line: usize, glyph: usize) -> Self {
|
||||
Self { line, glyph }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextBuffer<'a> {
|
||||
font_matches: &'a FontMatches<'a>,
|
||||
text_lines: Vec<String>,
|
||||
shape_lines: Vec<FontShapeLine<'a>>,
|
||||
layout_lines: Vec<FontLayoutLine<'a>>,
|
||||
font_size: i32,
|
||||
line_width: i32,
|
||||
pub cursor: TextCursor,
|
||||
pub redraw: bool,
|
||||
}
|
||||
|
||||
impl<'a> TextBuffer<'a> {
|
||||
pub fn new(font_matches: &'a FontMatches<'a>, text: &str, font_size: i32, line_width: i32) -> Self {
|
||||
let mut text_lines: Vec<String> = text.lines().map(String::from).collect();
|
||||
if text_lines.is_empty() {
|
||||
text_lines.push(String::new());
|
||||
}
|
||||
Self {
|
||||
font_matches,
|
||||
text_lines,
|
||||
shape_lines: Vec::new(),
|
||||
layout_lines: Vec::new(),
|
||||
font_size,
|
||||
line_width,
|
||||
cursor: TextCursor::default(),
|
||||
redraw: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shape_until(&mut self, lines: i32) {
|
||||
let instant = Instant::now();
|
||||
|
||||
let mut reshaped = 0;
|
||||
while self.shape_lines.len() < self.text_lines.len()
|
||||
&& (self.layout_lines.len() as i32) < lines
|
||||
{
|
||||
let line_i = FontLineIndex::new(self.shape_lines.len());
|
||||
self.reshape_line(line_i);
|
||||
reshaped += 1;
|
||||
}
|
||||
|
||||
let duration = instant.elapsed();
|
||||
if reshaped > 0 {
|
||||
log::debug!("shape_until {}: {:?}", reshaped, duration);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reshape_line(&mut self, line_i: FontLineIndex) {
|
||||
let instant = Instant::now();
|
||||
|
||||
let shape_line = self
|
||||
.font_matches
|
||||
.shape_line(line_i, &self.text_lines[line_i.get()]);
|
||||
if line_i.get() < self.shape_lines.len() {
|
||||
self.shape_lines[line_i.get()] = shape_line;
|
||||
} else {
|
||||
self.shape_lines.insert(line_i.get(), shape_line);
|
||||
}
|
||||
|
||||
let duration = instant.elapsed();
|
||||
log::debug!("reshape line {}: {:?}", line_i.get(), duration);
|
||||
|
||||
self.relayout_line(line_i);
|
||||
}
|
||||
|
||||
pub fn relayout(&mut self) {
|
||||
let instant = Instant::now();
|
||||
|
||||
self.layout_lines.clear();
|
||||
for line in self.shape_lines.iter() {
|
||||
let layout_i = self.layout_lines.len();
|
||||
line.layout(
|
||||
self.font_size,
|
||||
self.line_width,
|
||||
&mut self.layout_lines,
|
||||
layout_i,
|
||||
);
|
||||
}
|
||||
|
||||
self.redraw = true;
|
||||
|
||||
let duration = instant.elapsed();
|
||||
log::debug!("relayout: {:?}", duration);
|
||||
}
|
||||
|
||||
pub fn relayout_line(&mut self, line_i: FontLineIndex) {
|
||||
let instant = Instant::now();
|
||||
|
||||
let mut insert_opt = None;
|
||||
let mut layout_i = 0;
|
||||
while layout_i < self.layout_lines.len() {
|
||||
let layout_line = &self.layout_lines[layout_i];
|
||||
if layout_line.line_i == line_i {
|
||||
if insert_opt.is_none() {
|
||||
insert_opt = Some(layout_i);
|
||||
}
|
||||
self.layout_lines.remove(layout_i);
|
||||
} else {
|
||||
layout_i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let insert_i = insert_opt.unwrap_or(self.layout_lines.len());
|
||||
|
||||
let shape_line = &self.shape_lines[line_i.get()];
|
||||
shape_line.layout(
|
||||
self.font_size,
|
||||
self.line_width,
|
||||
&mut self.layout_lines,
|
||||
insert_i,
|
||||
);
|
||||
|
||||
self.redraw = true;
|
||||
|
||||
let duration = instant.elapsed();
|
||||
log::debug!("relayout line {}: {:?}", line_i.get(), duration);
|
||||
}
|
||||
|
||||
pub fn font_matches(&self) -> &FontMatches {
|
||||
&self.font_matches
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> i32 {
|
||||
self.font_size
|
||||
}
|
||||
|
||||
pub fn set_font_size(&mut self, font_size: i32) {
|
||||
self.font_size = font_size;
|
||||
self.relayout();
|
||||
}
|
||||
|
||||
pub fn line_width(&self) -> i32 {
|
||||
self.line_width
|
||||
}
|
||||
|
||||
pub fn set_line_width(&mut self, line_width: i32) {
|
||||
self.line_width = line_width;
|
||||
self.relayout();
|
||||
}
|
||||
|
||||
pub fn layout_lines(&self) -> &[FontLayoutLine] {
|
||||
&self.layout_lines
|
||||
}
|
||||
|
||||
pub fn text_lines(&self) -> &[String] {
|
||||
&self.text_lines
|
||||
}
|
||||
|
||||
pub fn action(&mut self, action: TextAction) {
|
||||
match action {
|
||||
TextAction::Left => {
|
||||
let line = &self.layout_lines[self.cursor.line];
|
||||
if self.cursor.glyph > line.glyphs.len() {
|
||||
self.cursor.glyph = line.glyphs.len();
|
||||
self.redraw = true;
|
||||
}
|
||||
if self.cursor.glyph > 0 {
|
||||
self.cursor.glyph -= 1;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
TextAction::Right => {
|
||||
let line = &self.layout_lines[self.cursor.line];
|
||||
if self.cursor.glyph > line.glyphs.len() {
|
||||
self.cursor.glyph = line.glyphs.len();
|
||||
self.redraw = true;
|
||||
}
|
||||
if self.cursor.glyph < line.glyphs.len() {
|
||||
self.cursor.glyph += 1;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
TextAction::Up => {
|
||||
if self.cursor.line > 0 {
|
||||
self.cursor.line -= 1;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
TextAction::Down => {
|
||||
if self.cursor.line + 1 < self.layout_lines.len() {
|
||||
self.cursor.line += 1;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
TextAction::Backspace => {
|
||||
let line = &self.layout_lines[self.cursor.line];
|
||||
if self.cursor.glyph > line.glyphs.len() {
|
||||
self.cursor.glyph = line.glyphs.len();
|
||||
self.redraw = true;
|
||||
}
|
||||
if self.cursor.glyph > 0 {
|
||||
self.cursor.glyph -= 1;
|
||||
let glyph = &line.glyphs[self.cursor.glyph];
|
||||
let text_line = &mut self.text_lines[line.line_i.get()];
|
||||
text_line.remove(glyph.start);
|
||||
self.reshape_line(line.line_i);
|
||||
}
|
||||
}
|
||||
TextAction::Delete => {
|
||||
let line = &self.layout_lines[self.cursor.line];
|
||||
if self.cursor.glyph < line.glyphs.len() {
|
||||
let glyph = &line.glyphs[self.cursor.glyph];
|
||||
let text_line = &mut self.text_lines[line.line_i.get()];
|
||||
text_line.remove(glyph.start);
|
||||
self.reshape_line(line.line_i);
|
||||
}
|
||||
}
|
||||
TextAction::Insert(character) => {
|
||||
let line = &self.layout_lines[self.cursor.line];
|
||||
if self.cursor.glyph >= line.glyphs.len() {
|
||||
match line.glyphs.last() {
|
||||
Some(glyph) => {
|
||||
let text_line = &mut self.text_lines[line.line_i.get()];
|
||||
text_line.insert(glyph.end, character);
|
||||
self.cursor.glyph += 1;
|
||||
self.reshape_line(line.line_i);
|
||||
}
|
||||
None => {
|
||||
let text_line = &mut self.text_lines[line.line_i.get()];
|
||||
text_line.push(character);
|
||||
self.cursor.glyph += 1;
|
||||
self.reshape_line(line.line_i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let glyph = &line.glyphs[self.cursor.glyph];
|
||||
let text_line = &mut self.text_lines[line.line_i.get()];
|
||||
text_line.insert(glyph.start, character);
|
||||
self.cursor.glyph += 1;
|
||||
self.reshape_line(line.line_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
132
src/font/cache.rs
Normal file
132
src/font/cache.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct CacheKey {
|
||||
pub glyph_id: u16,
|
||||
pub font_size: i32,
|
||||
pub x_bin: SubpixelBin,
|
||||
pub y_bin: SubpixelBin,
|
||||
}
|
||||
|
||||
impl CacheKey {
|
||||
pub fn new(glyph_id: u16, font_size: i32, pos: (f32, f32)) -> (Self, i32, i32) {
|
||||
let (x, x_bin) = SubpixelBin::new(pos.0);
|
||||
let (y, y_bin) = SubpixelBin::new(pos.1);
|
||||
(
|
||||
Self {
|
||||
glyph_id,
|
||||
font_size,
|
||||
x_bin,
|
||||
y_bin,
|
||||
},
|
||||
x,
|
||||
y,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub type CacheItem = Option<swash::scale::image::Image>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum SubpixelBin {
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
}
|
||||
|
||||
impl SubpixelBin {
|
||||
pub fn new(pos: f32) -> (i32, Self) {
|
||||
let trunc = pos.trunc() as i32;
|
||||
let fract = pos.fract();
|
||||
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 {
|
||||
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));
|
||||
}
|
||||
85
src/font/fallback/macos.rs
Normal file
85
src/font/fallback/macos.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use unicode_script::Script;
|
||||
|
||||
// Fallbacks to use after any script specific fallbacks
|
||||
pub fn common_fallback() -> &'static [&'static str] {
|
||||
&[
|
||||
".SF NS",
|
||||
"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"],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
138
src/font/fallback/mod.rs
Normal file
138
src/font/fallback/mod.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
use unicode_script::Script;
|
||||
|
||||
use super::Font;
|
||||
|
||||
use self::platform::*;
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
)))]
|
||||
#[path = "other.rs"]
|
||||
mod platform;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path = "macos.rs"]
|
||||
mod platform;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[path = "unix.rs"]
|
||||
mod platform;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path = "windows.rs"]
|
||||
mod platform;
|
||||
|
||||
pub struct FontFallbackIter<'a> {
|
||||
fonts: &'a [Font<'a>],
|
||||
default_family_opt: Option<&'a str>,
|
||||
scripts: Vec<Script>,
|
||||
locale: &'a str,
|
||||
script_i: (usize, usize),
|
||||
common_i: usize,
|
||||
other_i: usize,
|
||||
end: bool,
|
||||
}
|
||||
|
||||
impl<'a> FontFallbackIter<'a> {
|
||||
pub fn new(fonts: &'a [Font<'a>], default_family_opt: Option<&'a str>, scripts: Vec<Script>, locale: &'a str) -> Self {
|
||||
Self {
|
||||
fonts,
|
||||
default_family_opt,
|
||||
scripts,
|
||||
locale,
|
||||
script_i: (0, 0),
|
||||
common_i: 0,
|
||||
other_i: 0,
|
||||
end: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_missing(&self, word: &str) {
|
||||
if self.end {
|
||||
log::warn!(
|
||||
"Failed to find any fallback for {:?} locale '{}': '{}'",
|
||||
self.scripts,
|
||||
self.locale,
|
||||
word
|
||||
);
|
||||
} else if self.other_i > 0 {
|
||||
let font = &self.fonts[self.other_i - 1];
|
||||
log::warn!(
|
||||
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
|
||||
self.scripts,
|
||||
self.locale,
|
||||
font.info.family,
|
||||
word
|
||||
);
|
||||
} else if ! self.scripts.is_empty() && self.common_i > 0 {
|
||||
let family = common_fallback()[self.common_i - 1];
|
||||
log::debug!(
|
||||
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
|
||||
self.scripts,
|
||||
self.locale,
|
||||
family,
|
||||
word
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FontFallbackIter<'a> {
|
||||
type Item = &'a Font<'a>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(default_family) = self.default_family_opt.take() {
|
||||
for font in self.fonts.iter() {
|
||||
if font.info.family == default_family {
|
||||
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.locale);
|
||||
while self.script_i.1 < script_families.len() {
|
||||
let script_family = script_families[self.script_i.1];
|
||||
self.script_i.1 += 1;
|
||||
for font in self.fonts.iter() {
|
||||
if font.info.family == script_family {
|
||||
return Some(font);
|
||||
}
|
||||
}
|
||||
log::warn!("failed to find family '{}' for script {:?} and locale '{}'", script_family, script, self.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 font in self.fonts.iter() {
|
||||
if font.info.family == common_family {
|
||||
return Some(font);
|
||||
}
|
||||
}
|
||||
log::warn!("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.fonts.len() {
|
||||
let font = &self.fonts[self.other_i];
|
||||
self.other_i += 1;
|
||||
if ! forbidden_families.contains(&font.info.family.as_str()) {
|
||||
return Some(font);
|
||||
}
|
||||
}
|
||||
|
||||
self.end = true;
|
||||
None
|
||||
}
|
||||
}
|
||||
16
src/font/fallback/other.rs
Normal file
16
src/font/fallback/other.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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] {
|
||||
&[]
|
||||
}
|
||||
90
src/font/fallback/unix.rs
Normal file
90
src/font/fallback/unix.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
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)
|
||||
&[
|
||||
"DejaVu Sans",
|
||||
"FreeSans",
|
||||
"Noto Sans Symbols",
|
||||
"Noto Sans Symbols2",
|
||||
"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 JA"],
|
||||
// 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),
|
||||
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"],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
72
src/font/fallback/windows.rs
Normal file
72
src/font/fallback/windows.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
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"],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
96
src/font/layout.rs
Normal file
96
src/font/layout.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
use super::{CacheKey, Font, FontLineIndex};
|
||||
|
||||
pub struct FontLayoutGlyph<'a> {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub x: f32,
|
||||
pub w: f32,
|
||||
pub font: &'a Font<'a>,
|
||||
pub inner: (CacheKey, i32, i32),
|
||||
}
|
||||
|
||||
pub struct FontLayoutLine<'a> {
|
||||
pub line_i: FontLineIndex,
|
||||
pub glyphs: Vec<FontLayoutGlyph<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> FontLayoutLine<'a> {
|
||||
pub fn draw<F: FnMut(i32, i32, u32)>(&self, base: u32, mut f: F) {
|
||||
for glyph in self.glyphs.iter() {
|
||||
use swash::scale::{Render, Source, StrikeWith};
|
||||
use swash::zeno::{Format, Vector};
|
||||
|
||||
let mut cache = glyph.font.cache.lock().unwrap();
|
||||
|
||||
let (cache_key, x_int, y_int) = glyph.inner;
|
||||
|
||||
let image_opt = cache.entry(cache_key).or_insert_with(|| {
|
||||
let mut scale_context = glyph.font.scale_context.lock().unwrap();
|
||||
|
||||
// Build the scaler
|
||||
let mut scaler = scale_context
|
||||
.builder(glyph.font.swash)
|
||||
.size(cache_key.font_size as f32)
|
||||
.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)
|
||||
// Render the image
|
||||
.render(&mut scaler, cache_key.glyph_id)
|
||||
});
|
||||
|
||||
if let Some(ref image) = image_opt {
|
||||
use swash::scale::image::Content;
|
||||
|
||||
let x = x_int + image.placement.left;
|
||||
let y = y_int - 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 {
|
||||
let color = (image.data[i] as u32) << 24 | base & 0xFFFFFF;
|
||||
f(x + off_x, y + off_y, color);
|
||||
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 {
|
||||
let color = (image.data[i + 3] as u32) << 24
|
||||
| (image.data[i] as u32) << 16
|
||||
| (image.data[i + 1] as u32) << 8
|
||||
| (image.data[i + 2] as u32);
|
||||
f(x + off_x, y + off_y, color);
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
Content::SubpixelMask => {
|
||||
log::warn!("TODO: SubpixelMask");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
320
src/font/matches.rs
Normal file
320
src/font/matches.rs
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
use unicode_script::{Script, UnicodeScript};
|
||||
|
||||
use super::{Font, FontLineIndex, FontShapeGlyph, FontShapeLine, FontShapeSpan, FontShapeWord};
|
||||
use super::fallback::{FontFallbackIter};
|
||||
|
||||
pub struct FontMatches<'a> {
|
||||
pub locale: &'a str,
|
||||
pub fonts: Vec<Font<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> FontMatches<'a> {
|
||||
fn shape_fallback(
|
||||
&self,
|
||||
font: &'a Font<'a>,
|
||||
line: &str,
|
||||
start_word: usize,
|
||||
end_word: usize,
|
||||
span_rtl: bool,
|
||||
) -> (Vec<FontShapeGlyph>, Vec<usize>) {
|
||||
let word = &line[start_word..end_word];
|
||||
|
||||
let font_scale = font.rustybuzz.units_per_em() as f32;
|
||||
|
||||
let mut buffer = rustybuzz::UnicodeBuffer::new();
|
||||
buffer.set_direction(if span_rtl {
|
||||
rustybuzz::Direction::RightToLeft
|
||||
} else {
|
||||
rustybuzz::Direction::LeftToRight
|
||||
});
|
||||
buffer.push_str(word);
|
||||
buffer.guess_segment_properties();
|
||||
|
||||
let rtl = match buffer.direction() {
|
||||
rustybuzz::Direction::RightToLeft => true,
|
||||
//TODO: other directions?
|
||||
_ => false,
|
||||
};
|
||||
assert_eq!(rtl, span_rtl);
|
||||
|
||||
let glyph_buffer = rustybuzz::shape(&font.rustybuzz, &[], buffer);
|
||||
let glyph_infos = glyph_buffer.glyph_infos();
|
||||
let glyph_positions = glyph_buffer.glyph_positions();
|
||||
|
||||
let mut missing = Vec::new();
|
||||
let mut glyphs = Vec::with_capacity(glyph_infos.len());
|
||||
for (info, pos) in glyph_infos.iter().zip(glyph_positions.iter()) {
|
||||
let x_advance = pos.x_advance as f32 / font_scale;
|
||||
let y_advance = pos.y_advance as f32 / font_scale;
|
||||
let x_offset = pos.x_offset as f32 / font_scale;
|
||||
let y_offset = pos.y_offset as f32 / font_scale;
|
||||
|
||||
//println!(" {:?} {:?}", info, pos);
|
||||
if info.glyph_id == 0 {
|
||||
missing.push(start_word + info.cluster as usize);
|
||||
}
|
||||
|
||||
let inner = info.glyph_id as swash::GlyphId;
|
||||
glyphs.push(FontShapeGlyph {
|
||||
start: start_word + info.cluster as usize,
|
||||
end: end_word, // Set later
|
||||
x_advance,
|
||||
y_advance,
|
||||
x_offset,
|
||||
y_offset,
|
||||
font,
|
||||
inner,
|
||||
});
|
||||
}
|
||||
|
||||
// Adjust end of glyphs
|
||||
if rtl {
|
||||
for i in 1..glyphs.len() {
|
||||
let next_start = glyphs[i - 1].start;
|
||||
let next_end = glyphs[i - 1].end;
|
||||
let prev = &mut glyphs[i];
|
||||
if prev.start == next_start {
|
||||
prev.end = next_end;
|
||||
} else {
|
||||
prev.end = next_start;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i in (1..glyphs.len()).rev() {
|
||||
let next_start = glyphs[i].start;
|
||||
let next_end = glyphs[i].end;
|
||||
let prev = &mut glyphs[i - 1];
|
||||
if prev.start == next_start {
|
||||
prev.end = next_end;
|
||||
} else {
|
||||
prev.end = next_start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(glyphs, missing)
|
||||
}
|
||||
|
||||
fn shape_word(
|
||||
&self,
|
||||
line: &str,
|
||||
start_word: usize,
|
||||
end_word: usize,
|
||||
span_rtl: bool,
|
||||
blank: bool,
|
||||
) -> FontShapeWord {
|
||||
//TODO: use smallvec?
|
||||
let mut scripts = Vec::new();
|
||||
for c in line[start_word..end_word].chars() {
|
||||
match c.script() {
|
||||
Script::Common |
|
||||
Script::Inherited |
|
||||
Script::Latin |
|
||||
Script::Unknown => (),
|
||||
script => if ! scripts.contains(&script) {
|
||||
scripts.push(script);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
" Word {:?}{}: '{}'",
|
||||
scripts,
|
||||
if blank { " BLANK" } else { "" },
|
||||
&line[start_word..end_word],
|
||||
);
|
||||
|
||||
//TODO: configure default family
|
||||
let mut font_iter = FontFallbackIter::new(&self.fonts, Some("Fira Sans"), scripts, &self.locale);
|
||||
|
||||
let (mut glyphs, mut missing) = self.shape_fallback(
|
||||
font_iter.next().unwrap(),
|
||||
line,
|
||||
start_word,
|
||||
end_word,
|
||||
span_rtl
|
||||
);
|
||||
|
||||
//TODO: improve performance!
|
||||
while !missing.is_empty() {
|
||||
let font = match font_iter.next() {
|
||||
Some(some) => some,
|
||||
None => break,
|
||||
};
|
||||
|
||||
log::trace!("Evaluating fallback with font '{}'", font.info.family);
|
||||
let (mut fb_glyphs, fb_missing) = self.shape_fallback(
|
||||
font,
|
||||
line,
|
||||
start_word,
|
||||
end_word,
|
||||
span_rtl
|
||||
);
|
||||
|
||||
// Insert all matching glyphs
|
||||
let mut fb_i = 0;
|
||||
while fb_i < fb_glyphs.len() {
|
||||
let start = fb_glyphs[fb_i].start;
|
||||
let end = fb_glyphs[fb_i].end;
|
||||
|
||||
// Skip clusters that are not missing, or where the fallback font is missing
|
||||
if !missing.contains(&start) || fb_missing.contains(&start) {
|
||||
fb_i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut missing_i = 0;
|
||||
while missing_i < missing.len() {
|
||||
if missing[missing_i] >= start && missing[missing_i] < end {
|
||||
// println!("No longer missing {}", missing[missing_i]);
|
||||
missing.remove(missing_i);
|
||||
} else {
|
||||
missing_i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Find prior glyphs
|
||||
let mut i = 0;
|
||||
while i < glyphs.len() {
|
||||
if glyphs[i].start >= start && glyphs[i].end <= end {
|
||||
break;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove prior glyphs
|
||||
while i < glyphs.len() {
|
||||
if glyphs[i].start >= start && glyphs[i].end <= end {
|
||||
let _glyph = glyphs.remove(i);
|
||||
// log::trace!("Removed {},{} from {}", _glyph.start, _glyph.end, i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while fb_i < fb_glyphs.len() {
|
||||
if fb_glyphs[fb_i].start >= start && fb_glyphs[fb_i].end <= end {
|
||||
let fb_glyph = fb_glyphs.remove(fb_i);
|
||||
// log::trace!("Insert {},{} from font {} at {}", fb_glyph.start, fb_glyph.end, font_i, i);
|
||||
glyphs.insert(i, fb_glyph);
|
||||
i += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debug missing font fallbacks
|
||||
font_iter.check_missing(&line[start_word..end_word]);
|
||||
|
||||
/*
|
||||
for glyph in glyphs.iter() {
|
||||
log::trace!("'{}': {}, {}, {}, {}", &line[glyph.start..glyph.end], glyph.x_advance, glyph.y_advance, glyph.x_offset, glyph.y_offset);
|
||||
}
|
||||
*/
|
||||
|
||||
FontShapeWord { blank, glyphs }
|
||||
}
|
||||
|
||||
fn shape_span(
|
||||
&self,
|
||||
line: &str,
|
||||
start_span: usize,
|
||||
end_span: usize,
|
||||
line_rtl: bool,
|
||||
span_rtl: bool,
|
||||
) -> FontShapeSpan {
|
||||
let span = &line[start_span..end_span];
|
||||
|
||||
log::trace!(
|
||||
" Span {}: '{}'",
|
||||
if span_rtl { "RTL" } else { "LTR" },
|
||||
span
|
||||
);
|
||||
|
||||
let mut words = Vec::new();
|
||||
|
||||
let mut start_word = 0;
|
||||
for (end_lb, _) in unicode_linebreak::linebreaks(span) {
|
||||
let mut start_lb = end_lb;
|
||||
for (i, c) in span[start_word..end_lb].char_indices() {
|
||||
if start_word + i == end_lb {
|
||||
break;
|
||||
} else if c.is_whitespace() {
|
||||
start_lb = start_word + i;
|
||||
}
|
||||
}
|
||||
if start_word < start_lb {
|
||||
words.push(self.shape_word(
|
||||
line,
|
||||
start_span + start_word,
|
||||
start_span + start_lb,
|
||||
span_rtl,
|
||||
false,
|
||||
));
|
||||
}
|
||||
if start_lb < end_lb {
|
||||
words.push(self.shape_word(
|
||||
line,
|
||||
start_span + start_lb,
|
||||
start_span + end_lb,
|
||||
span_rtl,
|
||||
true,
|
||||
));
|
||||
}
|
||||
start_word = end_lb;
|
||||
}
|
||||
|
||||
// Reverse glyphs in RTL lines
|
||||
if line_rtl {
|
||||
for word in words.iter_mut() {
|
||||
word.glyphs.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse words in spans that do not match line direction
|
||||
if line_rtl != span_rtl {
|
||||
words.reverse();
|
||||
}
|
||||
|
||||
FontShapeSpan {
|
||||
rtl: span_rtl,
|
||||
words,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shape_line(&self, line_i: FontLineIndex, line: &str) -> FontShapeLine {
|
||||
let mut spans = Vec::new();
|
||||
|
||||
let bidi = unicode_bidi::BidiInfo::new(line, None);
|
||||
let rtl = if bidi.paragraphs.is_empty() {
|
||||
false
|
||||
} else {
|
||||
assert_eq!(bidi.paragraphs.len(), 1);
|
||||
let para_info = &bidi.paragraphs[0];
|
||||
let line_rtl = para_info.level.is_rtl();
|
||||
|
||||
log::trace!("Line {}: '{}'", if line_rtl { "RTL" } else { "LTR" }, line);
|
||||
|
||||
let paragraph = unicode_bidi::Paragraph::new(&bidi, ¶_info);
|
||||
|
||||
let mut start = 0;
|
||||
let mut span_rtl = line_rtl;
|
||||
for i in paragraph.para.range.clone() {
|
||||
let next_rtl = paragraph.info.levels[i].is_rtl();
|
||||
if span_rtl != next_rtl {
|
||||
spans.push(self.shape_span(line, start, i, line_rtl, span_rtl));
|
||||
span_rtl = next_rtl;
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
spans.push(self.shape_span(line, start, line.len(), line_rtl, span_rtl));
|
||||
|
||||
line_rtl
|
||||
};
|
||||
|
||||
FontShapeLine { line_i, rtl, spans }
|
||||
}
|
||||
}
|
||||
60
src/font/mod.rs
Normal file
60
src/font/mod.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
pub mod fallback;
|
||||
|
||||
pub use self::cache::*;
|
||||
mod cache;
|
||||
|
||||
pub use self::layout::*;
|
||||
mod layout;
|
||||
|
||||
pub use self::matches::*;
|
||||
mod matches;
|
||||
|
||||
pub use self::shape::*;
|
||||
mod shape;
|
||||
|
||||
pub use self::system::*;
|
||||
mod system;
|
||||
|
||||
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct FontCacheKey {
|
||||
glyph_id: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct FontLineIndex(usize);
|
||||
|
||||
impl FontLineIndex {
|
||||
pub fn new(index: usize) -> Self {
|
||||
Self(index)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Font<'a> {
|
||||
pub info: &'a fontdb::FaceInfo,
|
||||
pub data: &'a [u8],
|
||||
pub index: u32,
|
||||
pub rustybuzz: rustybuzz::Face<'a>,
|
||||
pub swash: swash::FontRef<'a>,
|
||||
pub scale_context: Mutex<swash::scale::ScaleContext>,
|
||||
pub cache: Mutex<HashMap<CacheKey, CacheItem>>,
|
||||
}
|
||||
|
||||
impl<'a> Font<'a> {
|
||||
pub fn new(info: &'a fontdb::FaceInfo, data: &'a [u8], index: u32) -> Option<Self> {
|
||||
Some(Self {
|
||||
info,
|
||||
data,
|
||||
index,
|
||||
rustybuzz: rustybuzz::Face::from_slice(data, index)?,
|
||||
swash: swash::FontRef::from_index(data, index as usize)?,
|
||||
scale_context: Mutex::new(swash::scale::ScaleContext::new()),
|
||||
cache: Mutex::new(HashMap::new()),
|
||||
})
|
||||
}
|
||||
}
|
||||
223
src/font/shape.rs
Normal file
223
src/font/shape.rs
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
use super::{CacheKey, Font, FontLayoutGlyph, FontLayoutLine, FontLineIndex};
|
||||
|
||||
pub struct FontShapeGlyph<'a> {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub x_advance: f32,
|
||||
pub y_advance: f32,
|
||||
pub x_offset: f32,
|
||||
pub y_offset: f32,
|
||||
pub font: &'a Font<'a>,
|
||||
pub inner: swash::GlyphId,
|
||||
}
|
||||
|
||||
impl<'a> FontShapeGlyph<'a> {
|
||||
fn layout(&self, font_size: i32, x: f32, y: f32) -> FontLayoutGlyph<'a> {
|
||||
let x_offset = font_size as f32 * self.x_offset;
|
||||
let y_offset = font_size as f32 * self.y_offset;
|
||||
let x_advance = font_size as f32 * self.x_advance;
|
||||
|
||||
let inner = CacheKey::new(self.inner, font_size, (x + x_offset, y - y_offset));
|
||||
FontLayoutGlyph {
|
||||
start: self.start,
|
||||
end: self.end,
|
||||
x: x,
|
||||
w: x_advance,
|
||||
font: self.font,
|
||||
inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FontShapeWord<'a> {
|
||||
pub blank: bool,
|
||||
pub glyphs: Vec<FontShapeGlyph<'a>>,
|
||||
}
|
||||
|
||||
pub struct FontShapeSpan<'a> {
|
||||
pub rtl: bool,
|
||||
pub words: Vec<FontShapeWord<'a>>,
|
||||
}
|
||||
|
||||
pub struct FontShapeLine<'a> {
|
||||
pub line_i: FontLineIndex,
|
||||
pub rtl: bool,
|
||||
pub spans: Vec<FontShapeSpan<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> FontShapeLine<'a> {
|
||||
pub fn layout(
|
||||
&self,
|
||||
font_size: i32,
|
||||
line_width: i32,
|
||||
layout_lines: &mut Vec<FontLayoutLine<'a>>,
|
||||
mut layout_i: usize,
|
||||
) {
|
||||
let mut push_line = true;
|
||||
let mut glyphs = Vec::new();
|
||||
|
||||
let start_x = if self.rtl { line_width as f32 } else { 0.0 };
|
||||
let end_x = if self.rtl { 0.0 } else { line_width as f32 };
|
||||
let mut x = start_x;
|
||||
let mut y = 0.0;
|
||||
for span in self.spans.iter() {
|
||||
//TODO: improve performance!
|
||||
let mut word_ranges = Vec::new();
|
||||
if self.rtl != span.rtl {
|
||||
let mut fit_x = x;
|
||||
let mut fitting_end = span.words.len();
|
||||
for i in (0..span.words.len()).rev() {
|
||||
let word = &span.words[i];
|
||||
|
||||
let mut word_size = 0.0;
|
||||
for glyph in word.glyphs.iter() {
|
||||
word_size += font_size as f32 * glyph.x_advance;
|
||||
}
|
||||
|
||||
let wrap = if self.rtl {
|
||||
fit_x - word_size < end_x
|
||||
} else {
|
||||
fit_x + word_size > end_x
|
||||
};
|
||||
|
||||
if wrap {
|
||||
let mut fitting_start = i + 1;
|
||||
while fitting_start < fitting_end {
|
||||
if span.words[fitting_start].blank {
|
||||
fitting_start += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
word_ranges.push((fitting_start..fitting_end, true));
|
||||
fitting_end = i + 1;
|
||||
|
||||
fit_x = start_x;
|
||||
}
|
||||
|
||||
if self.rtl {
|
||||
fit_x -= word_size;
|
||||
} else {
|
||||
fit_x += word_size;
|
||||
}
|
||||
}
|
||||
if !word_ranges.is_empty() {
|
||||
while fitting_end > 0 {
|
||||
if span.words[fitting_end - 1].blank {
|
||||
fitting_end -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
word_ranges.push((0..fitting_end, false));
|
||||
} else {
|
||||
let mut fit_x = x;
|
||||
let mut fitting_start = 0;
|
||||
for i in 0..span.words.len() {
|
||||
let word = &span.words[i];
|
||||
|
||||
let mut word_size = 0.0;
|
||||
for glyph in word.glyphs.iter() {
|
||||
word_size += font_size as f32 * glyph.x_advance;
|
||||
}
|
||||
|
||||
let wrap = if self.rtl {
|
||||
fit_x - word_size < end_x
|
||||
} else {
|
||||
fit_x + word_size > end_x
|
||||
};
|
||||
|
||||
if wrap {
|
||||
//TODO: skip blanks
|
||||
word_ranges.push((fitting_start..i, true));
|
||||
fitting_start = i;
|
||||
|
||||
fit_x = start_x;
|
||||
}
|
||||
|
||||
if self.rtl {
|
||||
fit_x -= word_size;
|
||||
} else {
|
||||
fit_x += word_size;
|
||||
}
|
||||
}
|
||||
word_ranges.push((fitting_start..span.words.len(), false));
|
||||
}
|
||||
|
||||
for (range, wrap) in word_ranges {
|
||||
for word in span.words[range].iter() {
|
||||
let mut word_size = 0.0;
|
||||
for glyph in word.glyphs.iter() {
|
||||
word_size += font_size as f32 * glyph.x_advance;
|
||||
}
|
||||
|
||||
//TODO: make wrapping optional
|
||||
let wrap = if self.rtl {
|
||||
x - word_size < end_x
|
||||
} else {
|
||||
x + word_size > end_x
|
||||
};
|
||||
if wrap && !glyphs.is_empty() {
|
||||
let mut glyphs_swap = Vec::new();
|
||||
std::mem::swap(&mut glyphs, &mut glyphs_swap);
|
||||
layout_lines.insert(
|
||||
layout_i,
|
||||
FontLayoutLine {
|
||||
line_i: self.line_i,
|
||||
glyphs: glyphs_swap,
|
||||
},
|
||||
);
|
||||
layout_i += 1;
|
||||
|
||||
x = start_x;
|
||||
y = 0.0;
|
||||
}
|
||||
|
||||
for glyph in word.glyphs.iter() {
|
||||
let x_advance = font_size as f32 * glyph.x_advance;
|
||||
let y_advance = font_size as f32 * glyph.y_advance;
|
||||
|
||||
if self.rtl {
|
||||
x -= x_advance
|
||||
}
|
||||
|
||||
glyphs.push(glyph.layout(font_size, x, y));
|
||||
push_line = true;
|
||||
|
||||
if !self.rtl {
|
||||
x += x_advance;
|
||||
}
|
||||
y += y_advance;
|
||||
}
|
||||
}
|
||||
|
||||
if wrap {
|
||||
let mut glyphs_swap = Vec::new();
|
||||
std::mem::swap(&mut glyphs, &mut glyphs_swap);
|
||||
layout_lines.insert(
|
||||
layout_i,
|
||||
FontLayoutLine {
|
||||
line_i: self.line_i,
|
||||
glyphs: glyphs_swap,
|
||||
},
|
||||
);
|
||||
layout_i += 1;
|
||||
|
||||
x = start_x;
|
||||
y = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if push_line {
|
||||
layout_lines.insert(
|
||||
layout_i,
|
||||
FontLayoutLine {
|
||||
line_i: self.line_i,
|
||||
glyphs,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/font/system.rs
Normal file
83
src/font/system.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use super::{Font, FontMatches};
|
||||
|
||||
pub struct FontSystem {
|
||||
pub locale: String,
|
||||
db: fontdb::Database,
|
||||
}
|
||||
|
||||
impl FontSystem {
|
||||
pub fn new() -> Self {
|
||||
let locale = sys_locale::get_locale().unwrap_or_else(|| {
|
||||
log::warn!("failed to get system locale, falling back to en-US");
|
||||
String::from("en-US")
|
||||
});
|
||||
log::info!("Locale: {}", locale);
|
||||
|
||||
let mut db = fontdb::Database::new();
|
||||
let now = std::time::Instant::now();
|
||||
db.load_system_fonts();
|
||||
//TODO: configurable default fonts
|
||||
db.set_monospace_family("Fira Mono");
|
||||
db.set_sans_serif_family("Fira Sans");
|
||||
db.set_serif_family("DejaVu Serif");
|
||||
log::info!(
|
||||
"Loaded {} font faces in {}ms.",
|
||||
db.len(),
|
||||
now.elapsed().as_millis()
|
||||
);
|
||||
|
||||
//TODO only do this on demand!
|
||||
assert_eq!(db.len(), db.faces().len());
|
||||
for i in 0..db.len() {
|
||||
let id = db.faces()[i].id;
|
||||
unsafe {
|
||||
db.make_shared_face_data(id);
|
||||
}
|
||||
}
|
||||
|
||||
Self { locale, db }
|
||||
}
|
||||
|
||||
pub fn matches<'a, F: Fn(&fontdb::FaceInfo) -> bool>(
|
||||
&'a self,
|
||||
f: F,
|
||||
) -> Option<FontMatches<'a>> {
|
||||
let mut fonts = Vec::new();
|
||||
for face in self.db.faces() {
|
||||
if !f(face) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let font_opt = Font::new(
|
||||
face,
|
||||
match &face.source {
|
||||
fontdb::Source::Binary(data) => data.deref().as_ref(),
|
||||
fontdb::Source::File(path) => {
|
||||
log::warn!("Unsupported fontdb Source::File('{}')", path.display());
|
||||
continue;
|
||||
}
|
||||
fontdb::Source::SharedFile(_path, data) => data.deref().as_ref(),
|
||||
},
|
||||
face.index,
|
||||
);
|
||||
|
||||
match font_opt {
|
||||
Some(font) => fonts.push(font),
|
||||
None => {
|
||||
log::warn!("failed to load font '{}'", face.post_script_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !fonts.is_empty() {
|
||||
Some(FontMatches {
|
||||
locale: &self.locale,
|
||||
fonts
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/lib.rs
Normal file
5
src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub use self::buffer::*;
|
||||
mod buffer;
|
||||
|
||||
pub use self::font::*;
|
||||
mod font;
|
||||
Loading…
Add table
Add a link
Reference in a new issue