Merge branch 'cosmic-design-system' into cosmic-design-system

This commit is contained in:
Eduardo Flores 2022-12-06 10:45:19 -07:00 • committed by GitHub
commit 95c902c8bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 1880 additions and 13328 deletions

View file

@ -14,35 +14,49 @@ debug = ["iced/debug"]
freedesktop-icons = "0.2.1" freedesktop-icons = "0.2.1"
apply = "0.3.0" apply = "0.3.0"
derive_setters = "0.1.5" derive_setters = "0.1.5"
lazy_static = "1.4.0"
palette = "0.6.1"
static-rc = "0.6.1"
[dependencies.cosmic-theme]
git = "https://github.com/pop-os/cosmic-theme.git"
[dependencies.iced] [dependencies.iced]
git = "https://github.com/pop-os/iced.git" git = "https://github.com/pop-os/iced.git"
branch = "cosmic-design-system" branch = "cosmic-design-system"
# path = "../iced" # path = "../iced"
features = ["cosmic-theme", "svg"] features = ["image", "svg"]
[dependencies.iced_style] [dependencies.iced_core]
git = "https://github.com/pop-os/iced.git" git = "https://github.com/pop-os/iced.git"
branch = "cosmic-design-system" branch = "cosmic-design-system"
# path = "../iced/style" # path = "../iced/core"
features = ["cosmic-theme"]
[dependencies.iced_native]
git = "https://github.com/pop-os/iced.git"
branch = "cosmic-design-system"
# path = "../iced/native"
features = ["cosmic-theme"]
[dependencies.iced_lazy] [dependencies.iced_lazy]
git = "https://github.com/pop-os/iced.git" git = "https://github.com/pop-os/iced.git"
branch = "cosmic-design-system" branch = "cosmic-design-system"
# path = "../iced/lazy" # path = "../iced/lazy"
[dependencies.iced_native]
git = "https://github.com/pop-os/iced.git"
branch = "cosmic-design-system"
# path = "../iced/native"
[dependencies.iced_style]
git = "https://github.com/pop-os/iced.git"
branch = "cosmic-design-system"
# path = "../iced/style"
[dependencies.iced_winit] [dependencies.iced_winit]
git = "https://github.com/pop-os/iced.git" git = "https://github.com/pop-os/iced.git"
branch = "cosmic-design-system" branch = "cosmic-design-system"
# path = "../iced/winit" # path = "../iced/winit"
[dependencies.iced_wgpu]
git = "https://github.com/pop-os/iced.git"
branch = "cosmic-design-system"
# path = "../iced/wgpu"
[workspace] [workspace]
members = [ members = [
"examples/*", "examples/*",

View file

@ -6,4 +6,5 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
apply = "0.3.0"
libcosmic = { path = "../..", features = ["debug"] } libcosmic = { path = "../..", features = ["debug"] }

View file

@ -1,3 +1,6 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use cosmic::{iced::Application, settings}; use cosmic::{iced::Application, settings};
mod window; mod window;

View file

@ -1,19 +1,24 @@
use cosmic::widget::{expander, nav_bar, nav_bar_item}; // Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use cosmic::{ use cosmic::{
iced::widget::{ iced::widget::{
checkbox, column, container, horizontal_space, pick_list, progress_bar, radio, row, slider, column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
text,
}, },
iced::{self, theme, Alignment, Application, Color, Command, Element, Length, Theme}, iced::{self, Alignment, Application, Command, Length},
iced_lazy::responsive, iced_lazy::responsive,
iced_winit::window::{drag, maximize, minimize}, iced_winit::window::{drag, toggle_maximize, minimize},
list_view, list_view_item, list_view_row, list_view_section, scrollable, theme::{self, Theme},
widget::{button, header_bar, list_box, list_row, list_view::*, toggler}, widget::{button, nav_button, nav_bar, nav_bar_page, nav_bar_section, header_bar, settings, scrollable, toggler, expander, nav_bar, nav_bar_item},
Element,
ElementExt,
}; };
use std::collections::BTreeMap; use std::{collections::BTreeMap, vec};
use theme::Button as ButtonTheme;
#[derive(Default)] #[derive(Default)]
pub struct Window { pub struct Window {
title: String,
page: usize, page: usize,
section: usize, section: usize,
debug: bool, debug: bool,
@ -78,11 +83,12 @@ impl Application for Window {
window.slider_value = 50.0; window.slider_value = 50.0;
// window.theme = Theme::Light; // window.theme = Theme::Light;
window.pick_list_selected = Some("Option 1"); window.pick_list_selected = Some("Option 1");
window.title = String::from("COSMIC Design System - Iced");
(window, Command::none()) (window, Command::none())
} }
fn title(&self) -> String { fn title(&self) -> String {
String::from("COSMIC Design System - Iced") self.title.clone()
} }
fn update(&mut self, message: Message) -> iced::Command<Self::Message> { fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
@ -101,8 +107,8 @@ impl Application for Window {
Message::Close => self.exit = true, Message::Close => self.exit = true,
Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled, Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled,
Message::Drag => return drag(), Message::Drag => return drag(),
Message::Minimize => return minimize(), Message::Minimize => return minimize(true),
Message::Maximize => return maximize(), Message::Maximize => return toggle_maximize(),
Message::RowSelected(row) => println!("Selected row {row}"), Message::RowSelected(row) => println!("Selected row {row}"),
} }
@ -112,21 +118,29 @@ impl Application for Window {
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
let mut header: Element<Message, _> = header_bar() let mut header: Element<Message, _> = header_bar()
.title(self.title()) .title(self.title())
// .nav_title(String::from("Settings")) //.nav_title(String::from("Settings"))
.sidebar_active(self.sidebar_toggled) .sidebar_active(self.sidebar_toggled)
.show_minimize(self.show_minimize) .show_minimize(self.show_minimize)
.show_maximize(self.show_maximize) .show_maximize(self.show_maximize)
.on_close(Message::Close) .on_close(Message::Close)
.on_drag(Message::Drag) .on_drag(Message::Drag)
.on_maximize(Message::Maximize) .start(
.on_minimize(Message::Minimize) nav_button("Settings")
.on_sidebar_toggle(Message::ToggleSidebar) .on_sidebar_toggled(Message::ToggleSidebar)
.into(); .sidebar_active(self.sidebar_toggled)
.into()
);
if self.debug { if self.show_maximize {
header = header.explain(Color::WHITE); header = header.on_maximize(Message::Maximize);
} }
if self.show_minimize {
header = header.on_minimize(Message::Minimize);
}
let header = Into::<Element<Message>>::into(header).debug(self.debug);
// TODO: Adding responsive makes this regenerate on every size change, and regeneration // TODO: Adding responsive makes this regenerate on every size change, and regeneration
// involves allocations for many different items. Ideally, we could only make the nav bar // involves allocations for many different items. Ideally, we could only make the nav bar
// responsive and leave the content to be sized normally. // responsive and leave the content to be sized normally.
@ -201,49 +215,48 @@ impl Application for Window {
}, },
); );
let content: Element<_> = list_view!( let content: Element<_> = settings::view_column(vec![
list_view_section!( settings::view_section("Debug")
"Debug", .add(settings::item("Debug theme", choose_theme))
list_view_item!("Debug theme", choose_theme), .add(settings::item(
list_view_item!(
"Debug layout", "Debug layout",
toggler(String::from("Debug layout"), self.debug, Message::Debug,) toggler(String::from("Debug layout"), self.debug, Message::Debug)
) ))
), .into(),
list_view_section!( settings::view_section("Buttons")
"Buttons", .add(settings::item_row(vec![
list_view_row!( button(ButtonTheme::Primary)
button!("Primary") .text("Primary")
.style(theme::Button::Primary) .on_press(Message::ButtonPressed)
.on_press(Message::ButtonPressed), .into(),
button!("Secondary") button(ButtonTheme::Secondary)
.style(theme::Button::Secondary) .text("Secondary")
.on_press(Message::ButtonPressed), .on_press(Message::ButtonPressed)
button!("Positive") .into(),
.style(theme::Button::Positive) button(ButtonTheme::Positive)
.on_press(Message::ButtonPressed), .text("Positive")
button!("Destructive") .on_press(Message::ButtonPressed)
.style(theme::Button::Destructive) .into(),
.on_press(Message::ButtonPressed), button(ButtonTheme::Destructive)
button!("Text") .text("Destructive")
.style(theme::Button::Text) .on_press(Message::ButtonPressed)
.on_press(Message::ButtonPressed), .into(),
), button(ButtonTheme::Text)
list_view_row!( .text("Text")
button!("Primary").style(theme::Button::Primary), .on_press(Message::ButtonPressed)
button!("Secondary").style(theme::Button::Secondary), .into()
button!("Positive").style(theme::Button::Positive), ]))
button!("Destructive").style(theme::Button::Destructive), .add(settings::item_row(vec![
button!("Text").style(theme::Button::Text), button(ButtonTheme::Primary).text("Primary").into(),
), button(ButtonTheme::Secondary).text("Secondary").into(),
), button(ButtonTheme::Positive).text("Positive").into(),
list_view_section!( button(ButtonTheme::Destructive).text("Destructive").into(),
"Controls", button(ButtonTheme::Text).text("Text").into(),
list_view_item!( ]))
"Toggler", .into(),
toggler(None, self.toggler_value, Message::TogglerToggled) settings::view_section("Controls")
), .add(settings::item("Toggler", toggler(None, self.toggler_value, Message::TogglerToggled)))
list_view_item!( .add(settings::item(
"Pick List (TODO)", "Pick List (TODO)",
pick_list( pick_list(
vec!["Option 1", "Option 2", "Option 3", "Option 4",], vec!["Option 1", "Option 2", "Option 3", "Option 4",],
@ -251,68 +264,30 @@ impl Application for Window {
Message::PickListSelected Message::PickListSelected
) )
.padding([8, 0, 8, 16]) .padding([8, 0, 8, 16])
), ))
list_view_item!( .add(settings::item(
"Slider", "Slider",
slider(0.0..=100.0, self.slider_value, Message::SliderChanged) slider(0.0..=100.0, self.slider_value, Message::SliderChanged)
.width(Length::Units(250)) .width(Length::Units(250))
), ))
list_view_item!( .add(settings::item(
"Progress", "Progress",
progress_bar(0.0..=100.0, self.slider_value) progress_bar(0.0..=100.0, self.slider_value)
.width(Length::Units(250)) .width(Length::Units(250))
.height(Length::Units(4)) .height(Length::Units(4))
), ))
checkbox("Checkbox", self.checkbox_value, Message::CheckboxToggled), .into()
),
list_view_section!(
"Expander",
expander()
.title("Label")
.subtitle("Caption")
.icon(String::from("edit-paste"))
.on_row_selected(Box::new(Message::RowSelected))
.rows(vec![
list_row()
.title("Label")
.subtitle("Caption")
.icon(String::from("help-about")),
list_row().subtitle("Caption").title("Label"),
list_row().title("Label")
]) ])
),
list_view_section!(
"List Box",
list_box()
.style(theme::Container::Custom(list_section_style))
.children(vec![
cosmic::list_box_row!("Title").into(),
cosmic::list_box_row!("Title", "Subtitle").into(),
cosmic::list_box_row!("Title", "", "edit-paste").into(),
cosmic::list_box_row!("", "Subtitle", "edit-paste").into(),
cosmic::list_box_row!("Title", "Subtitle", "edit-paste").into()
])
.render()
),
)
.into(); .into();
let mut widgets = Vec::with_capacity(2); let mut widgets = Vec::with_capacity(2);
widgets.push(if self.debug { widgets.push(sidebar.debug(self.debug));
sidebar.explain(Color::WHITE)
} else {
sidebar
});
widgets.push( widgets.push(
scrollable!(row![ scrollable(row![
horizontal_space(Length::Fill), horizontal_space(Length::Fill),
if self.debug { content.debug(self.debug),
content.explain(Color::WHITE)
} else {
content
},
horizontal_space(Length::Fill), horizontal_space(Length::Fill),
]) ])
.into(), .into(),

View file

@ -1,25 +0,0 @@
[package]
name = "text"
version = "0.1.0"
authors = []
edition = "2021"
publish = false
[dependencies]
ab_glyph = { version = "0.2", optional = true }
env_logger = "0.9"
fontdb = "0.9"
log = "0.4"
orbclient = "0.3"
memmap2 = "0.5"
rusttype = { version = "0.9", optional = true }
rustybuzz = "0.5"
swash = { version = "0.1", optional = true }
sys-locale = "0.2"
unicode-bidi = "0.3"
unicode-linebreak = "0.1"
unicode-script = "0.5"
[features]
default = ["swash"]
mono = []

View file

@ -1,14 +0,0 @@
# 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
```

View file

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

View file

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

View file

@ -1 +0,0 @@
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 "🐶🦺".

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,10 +0,0 @@
من از سیستم عامل پاپ! ۲۲.۰۴ استفاده میکنم.
متن راست به چپ Left to right text ادامه‌ی متن راست به چپ با اعداد ۱/۲۳۴ و English numbers 1.234.
مَتنِ فارسی و انگلیسی (English).
کشیده نویسی (کشــــــــــــیده‌نویسی).
علائم تُ تِ تَ تً تٍ تْ تٌ.
لام.
ویرگول (؛).
تای تأنیث یا ه دو نقطه (ة).
علامت تشدید (ــّـ).

View file

@ -1,29 +0,0 @@
https://en.wikipedia.org/wiki/Han_unification#Examples_of_language-dependent_glyphs
今
令
免
入
全
关
具
刃
化
外
情
才
抵
次
海
画
直
真
示
神
空
者
草
蔥
角
道
雇
骨

View file

@ -1,3 +0,0 @@
כאשר העולם רוצה לדבר, הוא מדבר ב־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.

View file

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

View file

@ -1,213 +0,0 @@
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: █
▉
╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
▝▀▘▙▄▟

View file

@ -1,218 +0,0 @@
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
􀀀 􀀁 􀀂 􀀃 􀀄 􀀅 􀀆 􀀇 􀀈 􀀉 􀀊 􀀋 􀀌 􀀍 􀀎 􀀏 􀀐 􀀑 􀀒 􀀓 􀀔 􀀕 􀀖 􀀗 􀀘 􀀙 􀀚 􀀛 􀀜 􀀝 􀀞 􀀟 􀀠 􀀡 􀀢 􀀣 􀀤 􀀥 􀀦 􀀧 􀀨 􀀩 􀀪 􀀫 􀀬 􀀭 􀀮 􀀯 􀀰 􀀱 􀀲 􀀳 􀀴 􀀵 􀀶 􀀷 􀀸 􀀹 􀀺 􀀻 􀀼 􀀽 􀀾 􀀿 􀁀 􀁁 􀁂 􀁃 􀁄 􀁅 􀁆 􀁇 􀁈 􀁉 􀁊 􀁋 􀁌 􀁍 􀁎 􀁏 􀁐 􀁑 􀁒 􀁓 􀁔 􀁕 􀁖 􀁗 􀁘 􀁙 􀁚 􀁛 􀁜 􀁝 􀁞 􀁟 􀁠 􀁡 􀁢 􀁣 􀁤 􀁥 􀁦 􀁧 􀁨 􀁩 􀁪 􀁫 􀁬 􀁭 􀁮 􀁯 􀁰 􀁱 􀁲 􀁳 􀁴 􀁵 􀁶 􀁷 􀁸 􀁹 􀁺 􀁻 􀁼 􀁽 􀁾 􀁿 ...

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,257 +0,0 @@
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, 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_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);
}
}
}
}
}

View file

@ -1,136 +0,0 @@
#[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,
)
}
}
#[cfg(not(feature = "swash"))]
pub type CacheItem = ();
#[cfg(feature = "swash")]
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));
}

View file

@ -1,85 +0,0 @@
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"],
_ => &[],
}
}

View file

@ -1,138 +0,0 @@
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
}
}

View file

@ -1,16 +0,0 @@
use unicode_script::Script;
// Fallbacks to use after any script specific fallbacks
pub fn common_fallback() -> &'static [&'static str] {
&[]
}
// Fallbacks to never use
pub fn forbidden_fallback() -> &'static [&'static str] {
&[]
}
// Fallbacks to use per script
pub fn script_fallback(script: &Script, locale: &str) -> &'static [&'static str] {
&[]
}

View file

@ -1,90 +0,0 @@
use unicode_script::Script;
// Fallbacks to use after any script specific fallbacks
pub fn common_fallback() -> &'static [&'static str] {
//TODO: abstract style (sans/serif/monospaced)
&[
"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"],
_ => &[],
}
}

View file

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

View file

@ -1,127 +0,0 @@
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>,
#[cfg(feature = "ab_glyph")]
pub inner: Option<ab_glyph::OutlinedGlyph>,
#[cfg(feature = "rusttype")]
pub inner: rusttype::PositionedGlyph<'a>,
#[cfg(feature = "swash")]
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() {
#[cfg(feature = "ab_glyph")]
if let Some(ref outline) = glyph.inner {
let bb = outline.px_bounds();
let x = bb.min.x as i32;
let y = bb.min.y as i32;
outline.draw(|off_x, off_y, v| {
//TODO: ensure v * 255.0 does not overflow!
let color = ((v * 255.0) as u32) << 24 | base & 0xFFFFFF;
f(x + off_x as i32, y + off_y as i32, color);
});
}
#[cfg(feature = "rusttype")]
if let Some(bb) = glyph.inner.pixel_bounding_box() {
let x = bb.min.x;
let y = bb.min.y;
glyph.inner.draw(|off_x, off_y, v| {
//TODO: ensure v * 255.0 does not overflow!
let color = ((v * 255.0) as u32) << 24 | base & 0xFFFFFF;
f(x + off_x as i32, y + off_y as i32, color);
});
}
#[cfg(feature = "swash")]
{
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");
}
}
}
}
}
}
}

View file

@ -1,331 +0,0 @@
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,
blank: 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);
}
#[cfg(feature = "ab_glyph")]
let inner = ab_glyph::GlyphId(info.glyph_id as u16);
#[cfg(feature = "rusttype")]
let inner = rusttype::GlyphId(info.glyph_id as u16);
#[cfg(feature = "swash")]
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,
blank
);
//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,
blank
);
// 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, &para_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 }
}
}

View file

@ -1,72 +0,0 @@
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>,
#[cfg(feature = "ab_glyph")]
pub ab_glyph: ab_glyph::FontRef<'a>,
#[cfg(feature = "rusttype")]
pub rusttype: rusttype::Font<'a>,
#[cfg(feature = "swash")]
pub swash: swash::FontRef<'a>,
#[cfg(feature = "swash")]
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)?,
#[cfg(feature = "ab_glyph")]
ab_glyph: ab_glyph::FontRef::try_from_slice_and_index(data, index).ok()?,
#[cfg(feature = "rusttype")]
rusttype: rusttype::Font::try_from_bytes_and_index(data, index)?,
#[cfg(feature = "swash")]
swash: swash::FontRef::from_index(data, index as usize)?,
#[cfg(feature = "swash")]
scale_context: Mutex::new(swash::scale::ScaleContext::new()),
cache: Mutex::new(HashMap::new()),
})
}
}

View file

@ -1,250 +0,0 @@
#[cfg(feature = "ab_glyph")]
use ab_glyph::Font as _;
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>,
#[cfg(feature = "ab_glyph")]
pub inner: ab_glyph::GlyphId,
#[cfg(feature = "rusttype")]
pub inner: rusttype::GlyphId,
#[cfg(feature = "swash")]
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;
#[cfg(feature = "ab_glyph")]
let inner = self
.font
.ab_glyph
.outline_glyph(self.inner.with_scale_and_position(
font_size as f32,
ab_glyph::point(x + x_offset, y - y_offset),
));
#[cfg(feature = "rusttype")]
let inner = self
.font
.rusttype
.glyph(self.inner)
.scaled(rusttype::Scale::uniform(font_size as f32))
.positioned(rusttype::point(x + x_offset, y - y_offset));
#[cfg(feature = "swash")]
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,
},
);
}
}
}

View file

@ -1,83 +0,0 @@
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
}
}
}

View file

@ -1,5 +0,0 @@
pub use self::buffer::*;
mod buffer;
pub use self::font::*;
mod font;

View file

@ -1,328 +0,0 @@
use orbclient::{Color, EventOption, Renderer, Window, WindowFlag};
use std::{cmp, env, fs, time::Instant};
use text::{FontLineIndex, FontSystem, TextAction, TextBuffer, TextCursor};
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!("../res/mono.txt");
#[cfg(not(feature = "mono"))]
let default_text = include_str!("../res/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,
_ => (),
}
}
}
}

19
src/ext.rs Normal file
View file

@ -0,0 +1,19 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use iced::Color;
pub trait ElementExt {
#[must_use]
fn debug(self, debug: bool) -> Self;
}
impl<'a, Message: 'static> ElementExt for crate::Element<'a, Message> {
fn debug(self, debug: bool) -> Self {
if debug {
self.explain(Color::WHITE)
} else {
self
}
}
}

View file

@ -1,3 +1,6 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
pub use iced::Font; pub use iced::Font;
pub const FONT: Font = Font::External { pub const FONT: Font = Font::External {

View file

@ -1,18 +1,24 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
pub use iced; pub use iced;
pub use iced_lazy; pub use iced_lazy;
pub use iced_native;
pub use iced_style;
pub use iced_winit; pub use iced_winit;
pub mod font; pub mod font;
pub mod theme;
pub mod widget; pub mod widget;
#[derive(Clone, Copy, Debug)] mod ext;
pub enum WindowMsg { pub use ext::ElementExt;
Close,
Drag, mod utils;
Minimize,
Maximize, pub use theme::Theme;
ToggleSidebar, pub type Renderer = iced::Renderer<Theme>;
} pub type Element<'a, Message> = iced::Element<'a, Message, Renderer>;
pub fn settings<Flags: Default>() -> iced::Settings<Flags> { pub fn settings<Flags: Default>() -> iced::Settings<Flags> {
let mut settings = iced::Settings::default(); let mut settings = iced::Settings::default();

2
src/theme/cosmic.rs Normal file
View file

@ -0,0 +1,2 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0

59
src/theme/expander.rs Normal file
View file

@ -0,0 +1,59 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use iced_core::{Background, Color};
/// The appearance of a [`Expander`](crate::native::expander::Expander).
#[derive(Clone, Copy, Debug)]
pub struct Appearance {
/// The background of the [`Expander`](crate::native::expander::Expander).
pub background: Background,
/// The border radius of the [`Expander`](crate::native::expander::Expander).
pub border_radius: f32,
/// The border width of the [`Expander`](crate::native::expander::Expander).
pub border_width: f32,
/// The border color of the [`Expander`](crate::native::expander::Expander).
pub border_color: Color,
/// The background of the head of the [`Expander`](crate::native::expander::Expander).
pub head_background: Background,
/// The text color of the head of the [`Expander`](crate::native::expander::Expander).
pub head_text_color: Color,
/// The background of the body of the [`Expander`](crate::native::expander::Expander).
pub body_background: Background,
/// The text color of the body of the [`Expander`](crate::native::expander::Expander).
pub body_text_color: Color,
/// The color of the close icon of the [`Expander`](crate::native::expander::Expander).
pub toggle_color: Color,
}
impl std::default::Default for Appearance {
fn default() -> Self {
Self {
background: Color::WHITE.into(),
border_radius: 10.0, //32.0,
border_width: 1.0,
border_color: [0.87, 0.87, 0.87].into(), //Color::BLACK.into(),
head_background: Background::Color([0.87, 0.87, 0.87].into()),
head_text_color: Color::BLACK,
body_background: Color::TRANSPARENT.into(),
body_text_color: Color::BLACK,
toggle_color: Color::BLACK,
}
}
}
/// A set of rules that dictate the [`Appearance`] of a container.
pub trait StyleSheet {
type Style: Default + Copy;
/// Produces the [`Appearance`] of a container.
fn appearance(&self, style: Self::Style) -> Appearance;
}

813
src/theme/mod.rs Normal file
View file

@ -0,0 +1,813 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
pub mod expander;
pub mod palette;
use std::hash::Hash;
use std::hash::Hasher;
pub use self::palette::Palette;
use iced_style::application;
use iced_style::button;
use iced_style::checkbox;
use iced_style::container;
use iced_style::menu;
use iced_style::pane_grid;
use iced_style::pick_list;
use iced_style::progress_bar;
use iced_style::radio;
use iced_style::rule;
use iced_style::scrollable;
use iced_style::slider;
use iced_style::svg;
use iced_style::text;
use iced_style::text_input;
use iced_style::toggler;
use iced_core::{Background, Color};
type CosmicColor = ::palette::rgb::Srgba;
type CosmicComponent = cosmic_theme::Component<CosmicColor>;
type CosmicTheme = cosmic_theme::Theme<CosmicColor>;
type CosmicThemeCss = cosmic_theme::Theme<cosmic_theme::util::CssColor>;
lazy_static::lazy_static! {
pub static ref COSMIC_DARK: CosmicTheme = CosmicThemeCss::dark_default().into_srgba();
pub static ref COSMIC_LIGHT: CosmicTheme = CosmicThemeCss::light_default().into_srgba();
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Theme {
Light,
Dark,
}
impl Theme {
#[must_use]
pub fn cosmic(self) -> &'static CosmicTheme {
match self {
Self::Dark => &COSMIC_DARK,
Self::Light => &COSMIC_LIGHT,
}
}
#[must_use]
pub fn palette(self) -> Palette {
match self {
Self::Dark => Palette::DARK,
Self::Light => Palette::LIGHT,
}
}
#[must_use]
pub fn extended_palette(&self) -> &self::palette::Extended {
match self {
Self::Dark => &self::palette::EXTENDED_DARK,
Self::Light => &self::palette::EXTENDED_LIGHT,
}
}
}
impl Default for Theme {
fn default() -> Self {
Self::Dark
}
}
#[derive(Debug, Clone, Copy)]
pub enum Application {
Default,
Custom(fn(Theme) -> application::Appearance),
}
impl Default for Application {
fn default() -> Self {
Self::Default
}
}
impl application::StyleSheet for Theme {
type Style = Application;
fn appearance(&self, style: &Self::Style) -> application::Appearance {
let cosmic = self.cosmic();
match style {
Application::Default => application::Appearance {
background_color: cosmic.bg_color().into(),
text_color: cosmic.on_bg_color().into(),
},
Application::Custom(f) => f(*self),
}
}
}
/*
* TODO: Button
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Button {
Deactivated,
Destructive,
Positive,
Primary,
Secondary,
Text,
}
impl Default for Button {
fn default() -> Self {
Self::Primary
}
}
impl Button {
fn cosmic(&self, theme: &Theme) -> &'static CosmicComponent {
let cosmic = theme.cosmic();
match self {
Button::Primary => &cosmic.accent,
Button::Secondary => &cosmic.primary.component,
Button::Positive => &cosmic.success,
Button::Destructive => &cosmic.destructive,
Button::Text => &cosmic.secondary.component,
Button::Deactivated => &cosmic.secondary.component,
}
}
}
impl button::StyleSheet for Theme {
type Style = Button;
fn active(&self, style: &Self::Style) -> button::Appearance {
let cosmic = style.cosmic(self);
button::Appearance {
border_radius: 24.0,
background: match style {
Button::Text => None,
_ => Some(Background::Color(cosmic.base.into())),
},
text_color: cosmic.on.into(),
..button::Appearance::default()
}
}
fn hovered(&self, style: &Self::Style) -> button::Appearance {
let active = self.active(&style);
let cosmic = style.cosmic(self);
button::Appearance {
background: Some(Background::Color(cosmic.hover.into())),
..active
}
}
}
/*
* TODO: Checkbox
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Checkbox {
Primary,
Secondary,
Success,
Danger,
}
impl Default for Checkbox {
fn default() -> Self {
Self::Primary
}
}
impl checkbox::StyleSheet for Theme {
type Style = Checkbox;
fn active(
&self,
style: &Self::Style,
is_checked: bool,
) -> checkbox::Appearance {
let palette = self.extended_palette();
match style {
Checkbox::Primary => checkbox_appearance(
palette.primary.strong.text,
palette.background.base,
palette.primary.strong,
is_checked,
),
Checkbox::Secondary => checkbox_appearance(
palette.background.base.text,
palette.background.base,
palette.background.base,
is_checked,
),
Checkbox::Success => checkbox_appearance(
palette.success.base.text,
palette.background.base,
palette.success.base,
is_checked,
),
Checkbox::Danger => checkbox_appearance(
palette.danger.base.text,
palette.background.base,
palette.danger.base,
is_checked,
),
}
}
fn hovered(
&self,
style: &Self::Style,
is_checked: bool,
) -> checkbox::Appearance {
let palette = self.extended_palette();
match style {
Checkbox::Primary => checkbox_appearance(
palette.primary.strong.text,
palette.background.weak,
palette.primary.base,
is_checked,
),
Checkbox::Secondary => checkbox_appearance(
palette.background.base.text,
palette.background.weak,
palette.background.base,
is_checked,
),
Checkbox::Success => checkbox_appearance(
palette.success.base.text,
palette.background.weak,
palette.success.base,
is_checked,
),
Checkbox::Danger => checkbox_appearance(
palette.danger.base.text,
palette.background.weak,
palette.danger.base,
is_checked,
),
}
}
}
fn checkbox_appearance(
checkmark_color: Color,
base: palette::Pair,
accent: palette::Pair,
is_checked: bool,
) -> checkbox::Appearance {
checkbox::Appearance {
background: Background::Color(if is_checked {
accent.color
} else {
base.color
}),
checkmark_color,
border_radius: 4.0,
border_width: if is_checked { 0.0 } else { 1.0 },
border_color: accent.color,
text_color: None,
}
}
#[derive(Clone, Copy)]
pub enum Expander {
Default,
Custom(fn(&Theme) -> expander::Appearance),
}
impl Default for Expander {
fn default() -> Self {
Self::Default
}
}
impl From<fn(&Theme) -> expander::Appearance> for Expander {
fn from(f: fn(&Theme) -> expander::Appearance) -> Self {
Self::Custom(f)
}
}
impl expander::StyleSheet for Theme {
type Style = Expander;
fn appearance(&self, style: Self::Style) -> expander::Appearance {
match style {
Expander::Default => expander::Appearance::default(),
Expander::Custom(f) => f(self),
}
}
}
/*
* TODO: Container
*/
#[derive(Clone, Copy)]
pub enum Container {
Transparent,
Box,
Custom(fn(&Theme) -> container::Appearance),
}
impl Default for Container {
fn default() -> Self {
Self::Transparent
}
}
impl From<fn(&Theme) -> container::Appearance> for Container {
fn from(f: fn(&Theme) -> container::Appearance) -> Self {
Self::Custom(f)
}
}
impl container::StyleSheet for Theme {
type Style = Container;
fn appearance(&self, style: &Self::Style) -> container::Appearance {
match style {
Container::Transparent => container::Appearance::default(),
Container::Box => {
let palette = self.extended_palette();
container::Appearance {
text_color: None,
background: palette.background.weak.color.into(),
border_radius: 2.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}
Container::Custom(f) => f(self),
}
}
}
/*
* Slider
*/
impl slider::StyleSheet for Theme {
type Style = ();
fn active(&self, _style: &Self::Style) -> slider::Appearance {
let cosmic = self.cosmic();
//TODO: no way to set rail thickness
slider::Appearance {
rail_colors: (
cosmic.accent.base.into(),
//TODO: no way to set color before/after slider
Color::TRANSPARENT,
),
handle: slider::Handle {
shape: slider::HandleShape::Circle {
radius: 10.0,
},
color: cosmic.accent.base.into(),
border_color: Color::TRANSPARENT,
border_width: 0.0,
}
}
}
fn hovered(&self, style: &Self::Style) -> slider::Appearance {
let mut style = self.active(&style);
style.handle.shape = slider::HandleShape::Circle {
radius: 16.0
};
style.handle.border_width = 6.0;
style.handle.border_color = match self {
Theme::Dark => Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.1),
Theme::Light => Color::from_rgba8(0, 0, 0, 0.1),
};
style
}
fn dragging(&self, style: &Self::Style) -> slider::Appearance {
let mut style = self.hovered(&style);
style.handle.border_color = match self {
Theme::Dark => Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.2),
Theme::Light => Color::from_rgba8(0, 0, 0, 0.2),
};
style
}
}
/*
* TODO: Menu
*/
impl menu::StyleSheet for Theme {
type Style = ();
fn appearance(&self, _style: &Self::Style) -> menu::Appearance {
let cosmic = self.cosmic();
menu::Appearance {
text_color: cosmic.primary.component.on.into(),
background: Background::Color(cosmic.background.base.into()),
border_width: 0.0,
border_radius: 16.0,
border_color: Color::TRANSPARENT,
selected_text_color: cosmic.primary.component.on.into(),
selected_background: Background::Color(cosmic.primary.component.hover.into()),
}
}
}
/*
* TODO: Pick List
*/
impl pick_list::StyleSheet for Theme {
type Style = ();
fn active(&self, _style: &()) -> pick_list::Appearance {
let cosmic = &self.cosmic().primary.component;
pick_list::Appearance {
text_color: cosmic.on.into(),
background: Color::TRANSPARENT.into(),
placeholder_color: cosmic.on.into(),
border_radius: 24.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
icon_size: 0.7,
}
}
fn hovered(&self, style: &()) -> pick_list::Appearance {
let cosmic = &self.cosmic().primary.component;
pick_list::Appearance {
background: Background::Color(cosmic.hover.into()),
..self.active(&style)
}
}
}
/*
* TODO: Radio
*/
impl radio::StyleSheet for Theme {
type Style = ();
fn active(&self, _style: &Self::Style, is_selected: bool) -> radio::Appearance {
let palette = self.extended_palette();
radio::Appearance {
background: Color::TRANSPARENT.into(),
dot_color: palette.primary.strong.color,
border_width: 1.0,
border_color: palette.primary.strong.color,
text_color: None,
}
}
fn hovered(&self, style: &Self::Style, is_selected: bool) -> radio::Appearance {
let active = self.active(&style, is_selected);
let palette = self.extended_palette();
radio::Appearance {
dot_color: palette.primary.strong.color,
background: palette.primary.weak.color.into(),
..active
}
}
}
/*
* Toggler
*/
impl toggler::StyleSheet for Theme {
type Style = ();
fn active(
&self,
_style: &Self::Style,
is_active: bool,
) -> toggler::Appearance {
let palette = self.palette();
toggler::Appearance {
background: if is_active {
palette.primary
} else {
//TODO: Grab neutral from palette
match self {
Theme::Dark => Color::from_rgb8(0x78, 0x78, 0x78),
Theme::Light => Color::from_rgb8(0x93, 0x93, 0x93),
}
},
background_border: None,
//TODO: Grab neutral from palette
foreground: match self {
Theme::Dark => Color::from_rgb8(0x27, 0x27, 0x27),
Theme::Light => Color::from_rgb8(0xe4, 0xe4, 0xe4),
},
foreground_border: None,
}
}
fn hovered(
&self,
style: &Self::Style,
is_active: bool,
) -> toggler::Appearance {
//TODO: grab colors from palette
match self {
Theme::Dark => toggler::Appearance {
background: if is_active {
Color::from_rgb8(0x9f, 0xed, 0xed)
} else {
Color::from_rgb8(0xb6, 0xb6, 0xb6)
},
..self.active(&style, is_active)
},
Theme::Light => toggler::Appearance {
background: if is_active {
Color::from_rgb8(0x00, 0x42, 0x62)
} else {
Color::from_rgb8(0x54, 0x54, 0x54)
},
..self.active(&style, is_active)
}
}
}
}
/*
* TODO: Pane Grid
*/
impl pane_grid::StyleSheet for Theme {
type Style = ();
fn picked_split(&self, _style: &Self::Style) -> Option<pane_grid::Line> {
let palette = self.extended_palette();
Some(pane_grid::Line {
color: palette.primary.strong.color,
width: 2.0,
})
}
fn hovered_split(&self, _style: &Self::Style) -> Option<pane_grid::Line> {
let palette = self.extended_palette();
Some(pane_grid::Line {
color: palette.primary.base.color,
width: 2.0,
})
}
}
/*
* TODO: Progress Bar
*/
#[derive(Clone, Copy)]
pub enum ProgressBar {
Primary,
Success,
Danger,
Custom(fn(&Theme) -> progress_bar::Appearance),
}
impl Default for ProgressBar {
fn default() -> Self {
Self::Primary
}
}
impl progress_bar::StyleSheet for Theme {
type Style = ProgressBar;
fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
let palette = self.extended_palette();
let from_palette = |bar: Color| progress_bar::Appearance {
background: palette.background.strong.color.into(),
bar: bar.into(),
border_radius: 2.0,
};
match style {
ProgressBar::Primary => from_palette(palette.primary.base.color),
ProgressBar::Success => from_palette(palette.success.base.color),
ProgressBar::Danger => from_palette(palette.danger.base.color),
ProgressBar::Custom(f) => f(self),
}
}
}
/*
* TODO: Rule
*/
#[derive(Clone, Copy)]
pub enum Rule {
Default,
Custom(fn(&Theme) -> rule::Appearance),
}
impl Default for Rule {
fn default() -> Self {
Self::Default
}
}
impl rule::StyleSheet for Theme {
type Style = Rule;
fn appearance(&self, style: &Self::Style) -> rule::Appearance {
let palette = self.extended_palette();
match style {
Rule::Default => rule::Appearance {
color: palette.background.strong.color,
width: 1,
radius: 0.0,
fill_mode: rule::FillMode::Full,
},
Rule::Custom(f) => f(self),
}
}
}
/*
* TODO: Scrollable
*/
impl scrollable::StyleSheet for Theme {
type Style = ();
fn active(&self, _style: &Self::Style) -> scrollable::Scrollbar {
let palette = self.extended_palette();
scrollable::Scrollbar {
background: palette.background.weak.color.into(),
border_radius: 4.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: scrollable::Scroller {
color: palette.background.strong.color,
border_radius: 4.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
}
fn hovered(&self, _style: &Self::Style) -> scrollable::Scrollbar {
let palette = self.extended_palette();
scrollable::Scrollbar {
background: palette.background.weak.color.into(),
border_radius: 4.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: scrollable::Scroller {
color: palette.primary.strong.color,
border_radius: 4.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
}
}
#[derive(Default, Clone, Copy)]
pub enum Svg {
/// Apply a custom appearance filter
Custom(fn(&Theme) -> svg::Appearance),
/// No filtering is applied
#[default]
Default,
/// Icon fill color will match text color
Symbolic,
/// Icon fill color will match accent color
SymbolicActive,
}
impl Hash for Svg {
fn hash<H: Hasher>(&self, state: &mut H) {
let id = match self {
Svg::Custom(_) => 0,
Svg::Default => 1,
Svg::Symbolic => 2,
Svg::SymbolicActive => 3
};
id.hash(state);
}
}
impl svg::StyleSheet for Theme {
type Style = Svg;
fn appearance(&self, style: Self::Style) -> svg::Appearance {
match style {
Svg::Default => svg::Appearance::default(),
Svg::Custom(appearance) => appearance(self),
Svg::Symbolic => svg::Appearance {
fill: Some(self.extended_palette().background.base.text),
},
Svg::SymbolicActive => svg::Appearance {
fill: Some(self.cosmic().accent.base.into()),
},
}
}
}
/*
* TODO: Text
*/
#[derive(Clone, Copy, Default)]
pub enum Text {
Accent,
#[default]
Default,
Color(Color),
Custom(fn(&Theme) -> text::Appearance),
}
impl From<Color> for Text {
fn from(color: Color) -> Self {
Text::Color(color)
}
}
impl text::StyleSheet for Theme {
type Style = Text;
fn appearance(&self, style: Self::Style) -> text::Appearance {
match style {
Text::Accent => text::Appearance {
color: Some(self.cosmic().accent.base.into())
},
Text::Default => text::Appearance::default(),
Text::Color(c) => text::Appearance { color: Some(c) },
Text::Custom(f) => f(self),
}
}
}
/*
* TODO: Text Input
*/
impl text_input::StyleSheet for Theme {
type Style = ();
fn active(&self, _style: &Self::Style) -> text_input::Appearance {
let palette = self.extended_palette();
text_input::Appearance {
background: palette.background.base.color.into(),
border_radius: 2.0,
border_width: 1.0,
border_color: palette.background.strong.color,
}
}
fn hovered(&self, _style: &Self::Style) -> text_input::Appearance {
let palette = self.extended_palette();
text_input::Appearance {
background: palette.background.base.color.into(),
border_radius: 2.0,
border_width: 1.0,
border_color: palette.background.base.text,
}
}
fn focused(&self, _style: &Self::Style) -> text_input::Appearance {
let palette = self.extended_palette();
text_input::Appearance {
background: palette.background.base.color.into(),
border_radius: 2.0,
border_width: 1.0,
border_color: palette.primary.strong.color,
}
}
fn placeholder_color(&self, _style: &Self::Style) -> Color {
let palette = self.extended_palette();
palette.background.strong.color
}
fn value_color(&self, _style: &Self::Style) -> Color {
let palette = self.extended_palette();
palette.background.base.text
}
fn selection_color(&self, _style: &Self::Style) -> Color {
let palette = self.extended_palette();
palette.primary.weak.color
}
}

299
src/theme/palette.rs Normal file
View file

@ -0,0 +1,299 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
//TODO: GET CORRECT PALETTE FROM COSMIC-THEME
use iced_core::Color;
use lazy_static::lazy_static;
use palette::{FromColor, Hsl, Mix, RelativeContrast, Srgb};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Palette {
pub background: Color,
pub text: Color,
pub primary: Color,
pub success: Color,
pub danger: Color,
}
impl Palette {
pub const LIGHT: Self = Self {
background: Color::from_rgb(
0xee as f32 / 255.0,
0xee as f32 / 255.0,
0xee as f32 / 255.0,
),
text: Color::from_rgb(
0x00 as f32 / 255.0,
0x00 as f32 / 255.0,
0x00 as f32 / 255.0,
),
primary: Color::from_rgb(
0x00 as f32 / 255.0,
0x49 as f32 / 255.0,
0x6d as f32 / 255.0,
),
success: Color::from_rgb(
0x3b as f32 / 255.0,
0x6e as f32 / 255.0,
0x43 as f32 / 255.0,
),
danger: Color::from_rgb(
0xa0 as f32 / 255.0,
0x25 as f32 / 255.0,
0x2b as f32 / 255.0,
),
};
pub const DARK: Self = Self {
background: Color::from_rgb(
0x1e as f32 / 255.0,
0x1e as f32 / 255.0,
0x1e as f32 / 255.0
),
text: Color::from_rgb(
0xe4 as f32 / 255.0,
0xe4 as f32 / 255.0,
0xe4 as f32 / 255.0,
),
primary: Color::from_rgb(
0x94 as f32 / 255.0,
0xeb as f32 / 255.0,
0xeb as f32 / 255.0,
),
success: Color::from_rgb(
0xac as f32 / 255.0,
0xf7 as f32 / 255.0,
0xd2 as f32 / 255.0,
),
danger: Color::from_rgb(
0xff as f32 / 255.0,
0xb5 as f32 / 255.0,
0xb5 as f32 / 255.0,
),
};
}
pub struct Extended {
pub background: Background,
pub primary: Primary,
pub secondary: Secondary,
pub success: Success,
pub danger: Danger,
}
lazy_static! {
pub static ref EXTENDED_LIGHT: Extended =
Extended::generate(Palette::LIGHT);
pub static ref EXTENDED_DARK: Extended = Extended::generate(Palette::DARK);
}
impl Extended {
#[must_use]
pub fn generate(palette: Palette) -> Self {
Self {
background: Background::new(palette.background, palette.text),
primary: Primary::generate(
palette.primary,
palette.background,
palette.text,
),
secondary: Secondary::generate(palette.background, palette.text),
success: Success::generate(
palette.success,
palette.background,
palette.text,
),
danger: Danger::generate(
palette.danger,
palette.background,
palette.text,
),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Pair {
pub color: Color,
pub text: Color,
}
impl Pair {
pub fn new(color: Color, text: Color) -> Self {
Self {
color,
text: readable(color, text),
}
}
}
pub struct Background {
pub base: Pair,
pub weak: Pair,
pub strong: Pair,
}
impl Background {
#[must_use]
pub fn new(base: Color, text: Color) -> Self {
let weak = mix(base, text, 0.15);
let strong = mix(base, text, 0.40);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
pub struct Primary {
pub base: Pair,
pub weak: Pair,
pub strong: Pair,
}
impl Primary {
#[must_use]
pub fn generate(base: Color, background: Color, text: Color) -> Self {
let weak = mix(base, background, 0.4);
let strong = deviate(base, 0.1);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
pub struct Secondary {
pub base: Pair,
pub weak: Pair,
pub strong: Pair,
}
impl Secondary {
#[must_use]
pub fn generate(base: Color, text: Color) -> Self {
let base = mix(base, text, 0.2);
let weak = mix(base, text, 0.1);
let strong = mix(base, text, 0.3);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
pub struct Success {
pub base: Pair,
pub weak: Pair,
pub strong: Pair,
}
impl Success {
#[must_use]
pub fn generate(base: Color, background: Color, text: Color) -> Self {
let weak = mix(base, background, 0.4);
let strong = deviate(base, 0.1);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
pub struct Danger {
pub base: Pair,
pub weak: Pair,
pub strong: Pair,
}
impl Danger {
#[must_use]
pub fn generate(base: Color, background: Color, text: Color) -> Self {
let weak = mix(base, background, 0.4);
let strong = deviate(base, 0.1);
Self {
base: Pair::new(base, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
}
}
}
fn darken(color: Color, amount: f32) -> Color {
let mut hsl = to_hsl(color);
hsl.lightness = if hsl.lightness - amount < 0.0 {
0.0
} else {
hsl.lightness - amount
};
from_hsl(hsl)
}
fn lighten(color: Color, amount: f32) -> Color {
let mut hsl = to_hsl(color);
hsl.lightness = if hsl.lightness + amount > 1.0 {
1.0
} else {
hsl.lightness + amount
};
from_hsl(hsl)
}
fn deviate(color: Color, amount: f32) -> Color {
if is_dark(color) {
lighten(color, amount)
} else {
darken(color, amount)
}
}
fn mix(a: Color, b: Color, factor: f32) -> Color {
let a_lin = Srgb::from(a).into_linear();
let b_lin = Srgb::from(b).into_linear();
let mixed = a_lin.mix(&b_lin, factor);
Srgb::from_linear(mixed).into()
}
fn readable(background: Color, text: Color) -> Color {
if is_readable(background, text) {
text
} else if is_dark(background) {
Color::WHITE
} else {
Color::BLACK
}
}
fn is_dark(color: Color) -> bool {
to_hsl(color).lightness < 0.6
}
fn is_readable(a: Color, b: Color) -> bool {
let a_srgb = Srgb::from(a);
let b_srgb = Srgb::from(b);
a_srgb.has_enhanced_contrast_text(&b_srgb)
}
fn to_hsl(color: Color) -> Hsl {
Hsl::from_color(Srgb::from(color))
}
fn from_hsl(hsl: Hsl) -> Color {
Srgb::from_color(hsl).into()
}

9
src/utils.rs Normal file
View file

@ -0,0 +1,9 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use static_rc::StaticRc;
/// Uses [`StaticRc`] to create two halves of value with shared ownership, with no runtime reference counting required.
pub(crate) fn static_rc_halves<T>(value: T) -> (StaticRc<T, 1, 3>, StaticRc<T, 2, 3>) {
StaticRc::split::<1, 2>(StaticRc::<T, 3, 3>::new(value))
}

View file

@ -1,13 +1,50 @@
#[macro_export] // Copyright 2022 System76 <info@system76.com>
macro_rules! button { // SPDX-License-Identifier: MPL-2.0
($($x:expr),+ $(,)?) => (
$crate::iced::widget::Button::new( use crate::{theme, Element, Renderer};
$crate::iced::widget::Row::with_children( use iced::widget;
vec![$($crate::iced::Element::from($x)),+]
) /// A button widget with COSMIC styling
.spacing(8) #[must_use]
) pub const fn button<Message>(style: theme::Button) -> Button<Message> {
.padding([8, 16]) Button { style, message: None }
);
} }
pub use button;
/// A button widget with COSMIC styling
pub struct Button<Message> {
style: theme::Button,
message: Option<Message>,
}
impl<Message: 'static> Button<Message> {
/// The message to emit on button press.
#[must_use]
pub fn on_press(mut self, message: Message) -> Self {
self.message = Some(message);
self
}
/// A button with an icon.
pub fn icon(self, style: theme::Svg, icon: &str, size: u16) -> widget::Button<Message, Renderer> {
self.custom(vec![super::icon(icon, size).style(style).into()])
}
/// A button with text.
pub fn text(self, text: &str) -> widget::Button<Message, Renderer> {
self.custom(vec![text.into()])
}
/// A custom button that has the desired default spacing and padding.
pub fn custom(self, children: Vec<Element<Message>>) -> widget::Button<Message, Renderer> {
let button = widget::button(widget::row(children).spacing(8))
.style(self.style)
.padding([8, 16]);
if let Some(message) = self.message {
button.on_press(message)
} else {
button
}
}
}

View file

@ -1,210 +0,0 @@
use std::vec;
use crate::{list_box_row, separator, widget::ListRow};
use apply::Apply;
use derive_setters::Setters;
use iced::{
theme,
widget::{self, button, container, horizontal_space, row, text, Column},
Alignment, Background, Element, Length, Renderer, Theme,
};
use iced_lazy::Component;
use iced_native::widget::{column, event_container};
#[derive(Setters)]
pub struct Expander<'a, Message> {
title: &'a str,
#[setters(strip_option)]
subtitle: Option<&'a str>,
#[setters(strip_option)]
icon: Option<String>,
expansible: bool,
#[setters(skip)]
rows: Option<Vec<ListRow<'a>>>,
#[setters(strip_option)]
on_row_selected: Option<Box<dyn Fn(usize) -> Message + 'a>>,
}
pub fn expander<'a, Message>() -> Expander<'a, Message> {
Expander {
title: "",
subtitle: None,
icon: None,
expansible: false,
rows: None,
on_row_selected: None,
}
}
pub struct ExpanderState {
pub expanded: bool,
}
impl Default for ExpanderState {
fn default() -> Self {
Self { expanded: true }
}
}
#[derive(Clone, Copy, Debug)]
pub enum ExpanderEvent {
Expand,
RowSelected(usize),
}
impl<'a, Message> Expander<'a, Message> {
pub fn rows(mut self, rows: Vec<ListRow<'a>>) -> Self {
self.rows = Some(rows);
self.expansible = true;
self
}
pub fn push(&mut self, row: ListRow<'a>) {
if self.rows.is_none() {
self.rows = Some(vec![])
}
self.rows.as_mut().unwrap().push(row);
}
}
impl<'a, Message: Clone + 'a> Component<Message, Renderer> for Expander<'a, Message> {
type State = ExpanderState;
type Event = ExpanderEvent;
fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
match event {
ExpanderEvent::Expand => {
state.expanded = !state.expanded;
None
}
ExpanderEvent::RowSelected(index) => self
.on_row_selected
.as_ref()
.map(|on_row_selected| (on_row_selected)(index)),
}
}
fn view(&self, state: &Self::State) -> Element<Self::Event> {
let heading: Element<ExpanderEvent, Renderer> = {
let mut captions = vec![text(&self.title).size(18).into()];
if let Some(subtitle) = &self.subtitle {
captions.push(text(subtitle).size(16).into());
}
let text = column(captions);
let space: Element<ExpanderEvent, Renderer> = horizontal_space(Length::Fill).into();
let toggler: Element<ExpanderEvent, Renderer> = {
let mut icon = super::icon(
if state.expanded {
"go-down-symbolic"
} else {
"go-next-symbolic"
},
16,
)
.apply(button)
.width(Length::Units(25));
if self.expansible {
icon = icon.on_press(ExpanderEvent::Expand);
}
icon.into()
};
let items = if let Some(icon) = &self.icon {
let icon = super::icon(icon.as_str(), 20)
.apply(event_container)
.padding(10);
row![icon, text, space, toggler]
} else {
row![text, space, toggler]
};
container(items.align_items(Alignment::Center))
.style(theme::Container::Custom(expander_heading_style))
.padding(10)
.into()
};
let rows: Vec<Element<_>> = if let Some(rows) = &self.rows {
rows.iter()
.enumerate()
.map(|(index, row)| {
let subtitle = row.subtitle.unwrap_or_default();
if let Some(icon) = &row.icon {
list_box_row!(row.title, subtitle, icon.as_str())
.apply(event_container)
.on_press(ExpanderEvent::RowSelected(index))
.into()
} else {
list_box_row!(row.title, subtitle)
.apply(event_container)
.on_press(ExpanderEvent::RowSelected(index))
.into()
}
})
.enumerate()
.flat_map(|(index, child)| {
if index != rows.len() - 1 {
vec![child, separator!(1).into()]
} else {
vec![child]
}
})
.collect()
} else {
vec![]
};
let rows: Element<ExpanderEvent> = Column::with_children(rows).into();
let mut layout = vec![heading];
if state.expanded && self.expansible {
layout.push(rows)
}
column(layout)
.apply(widget::container)
.height(Length::Shrink)
.style(theme::Container::Custom(expander_row_style))
.into()
}
}
impl<'a, Message: Clone + 'a> From<Expander<'a, Message>> for Element<'a, Message> {
fn from(expander: Expander<'a, Message>) -> Self {
iced_lazy::component(expander)
}
}
pub fn expander_heading_style(theme: &Theme) -> widget::container::Appearance {
let primary = &theme.cosmic().primary;
let accent = &theme.cosmic().accent;
widget::container::Appearance {
text_color: Some(accent.base.into()),
background: Some(Background::Color(primary.divider.into())),
border_radius: 8.0,
border_width: 0.0,
border_color: primary.on.into(),
}
}
pub fn expander_row_style(theme: &Theme) -> widget::container::Appearance {
let cosmic = &theme.cosmic().primary;
widget::container::Appearance {
text_color: Some(cosmic.on.into()),
background: Some(Background::Color(cosmic.base.into())),
border_radius: 8.0,
border_width: 0.4,
border_color: cosmic.divider.into(),
}
}
pub fn separator_style(theme: &Theme) -> widget::rule::Appearance {
let cosmic = &theme.cosmic().primary;
widget::rule::Appearance {
color: cosmic.divider.into(),
width: 1,
radius: 0.0,
fill_mode: widget::rule::FillMode::Padded(10),
}
}

View file

@ -1,15 +1,28 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use apply::Apply; use apply::Apply;
use derive_setters::*; use derive_setters::Setters;
use iced::{self, alignment::Vertical, theme, widget, Element, Length, Renderer}; use iced::{self, widget, Length};
use iced_lazy::Component; use crate::{theme, Element};
#[must_use]
pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
HeaderBar {
title: "",
on_close: None,
on_drag: None,
on_maximize: None,
on_minimize: None,
start: None,
center: None,
end: None,
}
}
#[derive(Setters)] #[derive(Setters)]
pub struct HeaderBar<Message> { pub struct HeaderBar<'a, Message> {
title: String, title: &'a str,
nav_title: String,
sidebar_active: bool,
show_minimize: bool,
show_maximize: bool,
#[setters(strip_option)] #[setters(strip_option)]
on_close: Option<Message>, on_close: Option<Message>,
#[setters(strip_option)] #[setters(strip_option)]
@ -19,108 +32,86 @@ pub struct HeaderBar<Message> {
#[setters(strip_option)] #[setters(strip_option)]
on_minimize: Option<Message>, on_minimize: Option<Message>,
#[setters(strip_option)] #[setters(strip_option)]
on_sidebar_toggle: Option<Message>, start: Option<Element<'a, Message>>,
#[setters(strip_option)]
center: Option<Element<'a, Message>>,
#[setters(strip_option)]
end: Option<Element<'a, Message>>
} }
pub fn header_bar<Message>() -> HeaderBar<Message> { impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
HeaderBar { /// Converts the headerbar builder into an Iced element.
title: String::default(), pub fn into_element(mut self) -> Element<'a, Message> {
nav_title: String::default(), let mut packed: Vec<Element<Message>> = Vec::with_capacity(4);
sidebar_active: false,
show_minimize: false,
show_maximize: false,
on_sidebar_toggle: None,
on_close: None,
on_drag: None,
on_maximize: None,
on_minimize: None,
}
}
#[derive(Debug, Clone)] if let Some(start) = self.start.take() {
pub enum HeaderEvent { packed.push(widget::container(start).align_x(iced::alignment::Horizontal::Left).into());
Close,
ToggleSidebar,
Drag,
Minimize,
Maximize,
}
impl<Message: Clone> Component<Message, Renderer> for HeaderBar<Message> {
type State = ();
type Event = HeaderEvent;
fn update(&mut self, _state: &mut Self::State, event: Self::Event) -> Option<Message> {
match event {
HeaderEvent::Close => self.on_close.clone(),
HeaderEvent::ToggleSidebar => self.on_sidebar_toggle.clone(),
HeaderEvent::Drag => self.on_drag.clone(),
HeaderEvent::Maximize => self.on_maximize.clone(),
HeaderEvent::Minimize => self.on_minimize.clone(),
}
} }
fn view(&self, _state: &Self::State) -> Element<Self::Event> { packed.push(if let Some(center) = self.center.take() {
let nav_button = { widget::container(center).align_x(iced::alignment::Horizontal::Center).into()
let text = widget::text(&self.nav_title)
.vertical_alignment(Vertical::Center)
.width(Length::Shrink)
.height(Length::Fill);
let icon = super::icon(
if self.sidebar_active {
"go-previous-symbolic"
} else { } else {
"go-next-symbolic" self.title_widget()
}, });
24,
)
.width(Length::Units(24))
.height(Length::Fill);
widget::row!(text, icon) packed.push(if let Some(end) = self.end.take() {
.padding(4) widget::row(vec![end, self.window_controls()])
.spacing(4)
.apply(widget::button)
.style(theme::Button::Primary)
.on_press(HeaderEvent::ToggleSidebar)
.apply(widget::container) .apply(widget::container)
.center_y() .align_x(iced::alignment::Horizontal::Right)
.height(Length::Fill)
.into() .into()
}; } else {
self.window_controls()
});
let content = widget::container(widget::text(&self.title)) let mut widget = widget::row(packed)
.height(Length::Units(50))
.padding(10)
.apply(widget::event_container)
.center_y();
if let Some(message) = self.on_drag.clone() {
widget = widget.on_press(message);
}
if let Some(message) = self.on_maximize.clone() {
widget = widget.on_release(message);
}
widget.into()
}
fn title_widget(&self) -> Element<'a, Message> {
widget::container(widget::text(self.title))
.center_x() .center_x()
.center_y() .center_y()
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.into(); .into()
}
let window_controls = { /// Creates the widget for window controls.
let mut widgets: Vec<Element<HeaderEvent, _>> = Vec::with_capacity(3); fn window_controls(&mut self) -> Element<'a, Message> {
let mut widgets: Vec<Element<_>> = Vec::with_capacity(3);
let icon = |name, size, on_press| { let icon = |name, size, on_press| {
super::icon(name, size) super::icon(name, size)
.apply(widget::button) .style(crate::theme::Svg::SymbolicActive)
.style(theme::Button::Primary) .apply(iced::widget::button)
.style(theme::Button::Text)
.on_press(on_press) .on_press(on_press)
}; };
if self.show_minimize { if let Some(message) = self.on_minimize.take() {
widgets.push(icon("window-minimize-symbolic", 16, HeaderEvent::Minimize).into()); widgets.push(icon("window-minimize-symbolic", 16, message).into());
} }
if self.show_maximize { if let Some(message) = self.on_maximize.take() {
widgets.push(icon("window-maximize-symbolic", 16, HeaderEvent::Maximize).into()); widgets.push(icon("window-maximize-symbolic", 16, message).into());
} }
widgets.push(icon("window-close-symbolic", 16, HeaderEvent::Close).into()); if let Some(message) = self.on_close.take() {
widgets.push(icon("window-close-symbolic", 16, message).into());
}
widget::row(widgets) widget::row(widgets)
.spacing(8) .spacing(8)
@ -128,21 +119,11 @@ impl<Message: Clone> Component<Message, Renderer> for HeaderBar<Message> {
.height(Length::Fill) .height(Length::Fill)
.center_y() .center_y()
.into() .into()
};
widget::row(vec![nav_button, content, window_controls])
.height(Length::Units(50))
.padding(10)
.apply(widget::event_container)
.center_y()
.on_press(HeaderEvent::Drag)
.on_release(HeaderEvent::Maximize)
.into()
} }
} }
impl<'a, Message: Clone + 'a> From<HeaderBar<Message>> for Element<'a, Message> { impl<'a, Message: Clone + 'static> From<HeaderBar<'a, Message>> for Element<'a, Message> {
fn from(header_bar: HeaderBar<Message>) -> Self { fn from(headerbar: HeaderBar<'a, Message>) -> Self {
iced_lazy::component(header_bar) headerbar.into_element()
} }
} }

View file

@ -1,20 +1,81 @@
use iced::{widget::svg, Length}; // Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
pub fn icon(name: &str, size: u16) -> svg::Svg { //! Lazily-generated SVG icon widget for Iced.
let handle = match freedesktop_icons::lookup(name)
.with_size(size) use std::borrow::Cow;
.with_theme("Pop") use std::hash::Hash;
use derive_setters::Setters;
use crate::{Element, Renderer};
use iced::{widget::svg, Length, ContentFit};
/// A lazily-generated SVG icon.
#[derive(Hash, Setters)]
pub struct Icon<'a> {
#[setters(skip)]
name: Cow<'a, str>,
#[setters(into)]
theme: Cow<'a, str>,
style: crate::theme::Svg,
size: u16,
#[setters(strip_option)]
content_fit: Option<ContentFit>,
#[setters(strip_option)]
width: Option<Length>,
#[setters(strip_option)]
height: Option<Length>,
}
/// A lazily-generated SVG icon.
#[must_use]
pub fn icon<'a>(name: impl Into<Cow<'a, str>>, size: u16) -> Icon<'a> {
Icon {
content_fit: None,
height: None,
name: name.into(),
size,
style: crate::theme::Svg::default(),
theme: Cow::Borrowed("Pop"),
width: None,
}
}
impl<'a> Icon<'a> {
#[must_use]
fn into_svg<Message: 'static>(self) -> Element<'a, Message> {
let (svg, svg_clone) = crate::utils::static_rc_halves(self);
iced_lazy::lazy(svg_clone, move || -> Element<Message> {
let icon = freedesktop_icons::lookup(&svg.name)
.with_size(svg.size)
.with_theme(&svg.theme)
.with_cache() .with_cache()
.force_svg() .force_svg()
.find() .find();
{
Some(path) => svg::Handle::from_path(path), let handle = if let Some(path) = icon {
None => { svg::Handle::from_path(path)
eprintln!("icon '{}' size {} not found", name, size); } else {
eprintln!("icon '{}' size {} not found", svg.name, svg.size);
svg::Handle::from_memory(Vec::new()) svg::Handle::from_memory(Vec::new())
}
}; };
svg::Svg::new(handle)
.width(Length::Units(size)) let mut widget = svg::Svg::<Renderer>::new(handle)
.height(Length::Units(size)) .style(svg.style)
.width(svg.width.unwrap_or(Length::Units(svg.size)))
.height(svg.height.unwrap_or(Length::Units(svg.size)));
if let Some(content_fit) = svg.content_fit {
widget = widget.content_fit(content_fit);
}
widget.into()
}).into()
}
}
impl<'a, Message: 'static> From<Icon<'a>> for Element<'a, Message> {
fn from(icon: Icon<'a>) -> Self {
icon.into_svg::<Message>()
}
} }

65
src/widget/list/column.rs Normal file
View file

@ -0,0 +1,65 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use apply::Apply;
use crate::{Element, theme};
use crate::widget::horizontal_rule;
use iced::{Background, Color};
#[must_use]
pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
ListColumn::default()
}
pub struct ListColumn<'a, Message> {
children: Vec<Element<'a, Message>>,
}
impl<'a, Message: 'static> Default for ListColumn<'a, Message> {
fn default() -> Self {
Self { children: Vec::with_capacity(4) }
}
}
impl<'a, Message: 'static> ListColumn<'a, Message> {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn add(mut self, item: impl Into<Element<'a, Message>>) -> Self {
if !self.children.is_empty() {
self.children.push(horizontal_rule(12).into());
}
self.children.push(item.into());
self
}
#[must_use]
pub fn into_element(self) -> Element<'a, Message> {
iced::widget::column(self.children)
.spacing(12)
.apply(iced::widget::container)
.padding([12, 16])
.style(theme::Container::Custom(style))
.into()
}
}
impl<'a, Message: 'static> From<ListColumn<'a, Message>> for Element<'a, Message> {
fn from(column: ListColumn<'a, Message>) -> Self {
column.into_element()
}
}
fn style(theme: &crate::Theme) -> iced::widget::container::Appearance {
let cosmic = &theme.cosmic().primary;
iced::widget::container::Appearance {
text_color: Some(cosmic.on.into()),
background: Some(Background::Color(cosmic.base.into())),
border_radius: 8.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}

2
src/widget/list/item.rs Normal file
View file

@ -0,0 +1,2 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0

View file

@ -1,453 +0,0 @@
use crate::separator;
use derive_setters::Setters;
use iced::mouse::Interaction;
use iced::{overlay, Alignment, Length, Padding, Point, Rectangle};
use iced_native::event::Status;
use iced_native::layout::flex::{resolve, Axis};
use iced_native::layout::{Limits, Node};
use iced_native::overlay::from_children;
use iced_native::renderer::Style;
use iced_native::widget::{column, Operation, Tree};
use iced_native::{
renderer, row, Background, Clipboard, Color, Element, Event, Layout, Shell, Widget,
};
use iced_style::container::{Appearance, StyleSheet};
use iced_style::theme;
use iced_style::theme::Container;
#[derive(Setters)]
#[allow(dead_code)]
pub struct ListBox<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Renderer::Theme: StyleSheet + iced_style::rule::StyleSheet,
<Renderer as iced_native::Renderer>::Theme: iced_style::rule::StyleSheet,
{
spacing: u16,
#[setters(into)]
padding: Padding,
width: Length,
height: Length,
max_width: u32,
align_items: Alignment,
style: <Renderer::Theme as StyleSheet>::Style,
children: Vec<Element<'a, Message, Renderer>>,
#[setters(strip_option)]
placeholder: Option<Element<'a, Message, Renderer>>,
show_separators: bool,
on_item_selected: Option<Box<dyn Fn(usize) -> Message + 'a>>,
}
pub fn list_box<'a, Message: 'a, Renderer>() -> ListBox<'a, Message, Renderer>
where
Renderer: iced_native::Renderer + 'a,
<<Renderer as iced_native::Renderer>::Theme as StyleSheet>::Style: From<Container>,
<Renderer as iced_native::Renderer>::Theme: StyleSheet + iced_style::rule::StyleSheet,
<<Renderer as iced_native::Renderer>::Theme as iced_style::rule::StyleSheet>::Style:
From<theme::Rule>,
{
ListBox::new()
}
impl<'a, Message: 'a, Renderer: iced_native::Renderer + 'a> ListBox<'a, Message, Renderer>
where
Renderer::Theme: StyleSheet + iced_style::rule::StyleSheet,
<<Renderer as iced_native::Renderer>::Theme as StyleSheet>::Style: From<Container>,
<<Renderer as iced_native::Renderer>::Theme as iced_style::rule::StyleSheet>::Style:
From<theme::Rule>,
{
/// The default padding of a [`ListBox`] drawn by this renderer.
pub const DEFAULT_PADDING: u16 = 0;
/// Creates an empty [`ListBox`].
pub fn new() -> Self {
Self::with_children(Vec::<Element<Message, Renderer>>::new()).render()
}
/// Creates a new [`ListBox`].
///
/// [`ListBox`]: struct.ListBox.html
pub fn with_children(children: Vec<Element<'a, Message, Renderer>>) -> Self {
let list_box = Self {
spacing: 0,
padding: Padding::from(Self::DEFAULT_PADDING),
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
align_items: Alignment::Center,
style: Default::default(),
children,
placeholder: None,
show_separators: true,
on_item_selected: None,
};
list_box.render()
}
pub fn render(mut self) -> Self {
let children_size = self.children.len();
self.children = self
.children
.into_iter()
.enumerate()
.map(|(index, child)| {
let row_items = if self.show_separators && index != children_size - 1 {
vec![
row![child].align_items(self.align_items).into(),
separator!(1).into(),
]
} else {
vec![row![child].align_items(self.align_items).into()]
};
column(row_items).spacing(self.spacing).into()
})
.collect();
self
}
/// Adds an element to the [`ListBox`].
pub fn push(mut self, child: impl Into<Element<'a, Message, Renderer>>) -> Self {
self.children.push(child.into());
self = self.render();
self
}
}
impl<'a, Message: 'a, Renderer: iced_native::Renderer + 'a> std::default::Default
for ListBox<'a, Message, Renderer>
where
Renderer::Theme: StyleSheet + iced_style::rule::StyleSheet,
<<Renderer as iced_native::Renderer>::Theme as StyleSheet>::Style: From<Container>,
<<Renderer as iced_native::Renderer>::Theme as iced_style::rule::StyleSheet>::Style:
From<theme::Rule>,
{
fn default() -> Self {
Self::new()
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for ListBox<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
<Renderer as iced_native::Renderer>::Theme: StyleSheet + iced_style::rule::StyleSheet,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node {
let limits = limits
.max_width(self.max_width)
.width(self.width)
.height(self.height);
if !self.children.is_empty() {
resolve(
Axis::Vertical,
renderer,
&limits,
self.padding,
self.spacing as f32,
self.align_items,
&self.children,
)
} else if self.placeholder.is_some() {
self.placeholder
.as_ref()
.unwrap()
.as_widget()
.layout(renderer, &limits)
} else {
Node::default()
}
}
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
let color_scheme = theme.appearance(self.style);
draw_background(renderer, &color_scheme, layout.bounds());
if !self.children.is_empty() {
for ((child, state), layout) in self
.children
.iter()
.zip(&state.children)
.zip(layout.children())
{
child.as_widget().draw(
state,
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
} else if let Some(placeholder) = &self.placeholder {
placeholder.as_widget().draw(
state,
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
}
fn children(&self) -> Vec<Tree> {
let widgets = if !self.children.is_empty() {
self.children.iter().map(Tree::new).collect()
} else if let Some(placeholder) = &self.placeholder {
vec![Tree::new(placeholder)]
} else {
vec![Tree::empty()]
};
widgets
}
fn diff(&self, tree: &mut Tree) {
if !self.children.is_empty() {
tree.diff_children(&self.children);
} else if let Some(placeholder) = &self.placeholder {
tree.diff_children(&[placeholder]);
}
}
fn operate(
&self,
state: &mut Tree,
layout: Layout<'_>,
operation: &mut dyn Operation<Message>,
) {
if !self.children.is_empty() {
operation.container(None, &mut |operation| {
self.children
.iter()
.zip(&mut state.children)
.zip(layout.children())
.for_each(|((child, state), layout)| {
child.as_widget().operate(state, layout, operation);
})
});
} else if let Some(placeholder) = &self.placeholder {
placeholder.as_widget().operate(state, layout, operation);
}
}
fn on_event(
&mut self,
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> Status {
if !self.children.is_empty() {
self.children
.iter_mut()
.zip(&mut state.children)
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget_mut().on_event(
state,
event.clone(),
layout,
cursor_position,
renderer,
clipboard,
shell,
)
})
.fold(Status::Ignored, Status::merge)
} else if self.placeholder.is_some() {
self.placeholder.as_mut().unwrap().as_widget_mut().on_event(
&mut state.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
} else {
Status::Ignored
}
}
fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> Interaction {
if !self.children.is_empty() {
self.children
.iter()
.zip(&state.children)
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
state,
layout,
cursor_position,
viewport,
renderer,
)
})
.max()
.unwrap_or_default()
} else if let Some(placeholder) = &self.placeholder {
placeholder.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
viewport,
renderer,
)
} else {
Interaction::Idle
}
}
fn overlay<'b>(
&'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
if !self.children.is_empty() {
from_children(&self.children, tree, layout, renderer)
} else if let Some(placeholder) = &self.placeholder {
placeholder
.as_widget()
.overlay(&mut tree.children[0], layout, renderer)
} else {
None
}
}
}
/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
pub fn draw_background<Renderer>(
renderer: &mut Renderer,
appearance: &Appearance,
bounds: Rectangle,
) where
Renderer: iced_native::Renderer,
{
if appearance.background.is_some() || appearance.border_width > 0.0 {
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: appearance.border_radius,
border_width: appearance.border_width,
border_color: appearance.border_color,
},
appearance
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
}
}
impl<'a, Message: 'a, Renderer: iced_native::Renderer + 'a> From<ListBox<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
<Renderer as iced_native::Renderer>::Theme: StyleSheet + iced_style::rule::StyleSheet,
{
fn from(list_box: ListBox<'a, Message, Renderer>) -> Self {
Self::new(list_box)
}
}
#[macro_export]
macro_rules! list_box_item {
($($x:expr),+ $(,)?) => (
$crate::iced::widget::row![
column(vec![
$($x),+
])
]
);
}
pub use list_box_item;
#[macro_export]
macro_rules! list_box_heading {
($title:expr) => {
$crate::iced::widget::container(
$crate::iced::widget::row![
text($title).size(18),
$crate::iced::widget::vertical_space(Length::Fill),
$crate::iced::widget::horizontal_space(Length::Fill)
]
.height(Length::Fill)
.align_items($crate::iced::alignment::Alignment::Center),
)
.style($crate::iced::theme::Container::Custom(
$crate::widget::expander_heading_style,
))
.max_height(60)
.padding(10)
};
($title:expr, $subtitle:expr) => {
$crate::iced::widget::container(
$crate::iced::widget::row![
column(vec![
text($title).size(18).into(),
text($subtitle).size(16).into(),
]),
$crate::iced::widget::vertical_space(Length::Fill),
$crate::iced::widget::horizontal_space(Length::Fill)
]
.height(Length::Fill)
.align_items($crate::iced::alignment::Alignment::Center),
)
.style($crate::iced::theme::Container::Custom(
$crate::widget::expander_heading_style,
))
.max_height(60)
.padding(10)
};
($title:expr, $subtitle:expr, $icon:expr) => {
$crate::iced::widget::container(
$crate::iced::widget::row![
container($crate::widget::icon($icon, 20)).padding(10),
column(vec![
text($title).size(18).into(),
text($subtitle).size(16).into(),
]),
$crate::iced::widget::vertical_space(Length::Fill),
$crate::iced::widget::horizontal_space(Length::Fill)
]
.height(Length::Fill)
.align_items($crate::iced::alignment::Alignment::Center),
)
.style($crate::iced::theme::Container::Custom(
$crate::widget::expander_heading_style,
))
.max_height(60)
.padding(10)
};
}
pub use list_box_heading;

View file

@ -1,18 +0,0 @@
use derive_setters::Setters;
#[derive(Setters, Default, Debug, Clone)]
pub struct ListRow<'a> {
pub(crate) title: &'a str,
#[setters(strip_option)]
pub subtitle: Option<&'a str>,
#[setters(strip_option)]
pub icon: Option<String>,
}
pub fn list_row<'a>() -> ListRow<'a> {
ListRow {
title: "",
subtitle: None,
icon: None,
}
}

View file

@ -1,145 +0,0 @@
pub use iced::{widget, Background, Color, Theme};
pub mod list_view {
#[macro_export]
macro_rules! list_view {
($($x:expr),+ $(,)?) => (
$crate::iced::widget::Column::with_children(
vec![$($crate::iced::Element::from($x)),+]
)
.spacing(24)
.padding(24)
.max_width(600)
);
}
#[macro_export]
macro_rules! list_view_row {
($($x:expr),+ $(,)?) => (
$crate::iced::widget::Row::with_children(vec![
$($crate::iced::Element::from($x)),+
])
.align_items(Alignment::Center)
.padding([0, 8])
.spacing(12)
);
}
#[macro_export]
macro_rules! list_view_section {
($title:expr, $($x:expr),+ $(,)?) => (
$crate::iced::widget::Column::with_children(vec![
$crate::iced::widget::Text::new($title)
.font($crate::font::FONT_SEMIBOLD)
.into()
,
$crate::iced::widget::Container::new({
let mut children = vec![$($crate::iced::Element::from($x)),+];
//TODO: more efficient method for adding separators
let mut i = 1;
while i < children.len() {
children.insert(i, $crate::separator!(12).into());
i += 2;
}
$crate::iced::widget::Column::with_children(children)
.spacing(12)
})
.padding([12, 16])
.style(theme::Container::Custom(
list_section_style
))
.into()
])
.spacing(8)
);
}
#[macro_export]
macro_rules! list_view_item {
($title:expr, $($x:expr),+ $(,)?) => (
$crate::list_view_row!(
$crate::iced::widget::Text::new($title),
$crate::iced::widget::horizontal_space(
$crate::iced::Length::Fill
),
$($x),+
)
);
}
pub fn list_section_style(theme: &Theme) -> widget::container::Appearance {
let cosmic = &theme.cosmic().primary;
widget::container::Appearance {
text_color: Some(cosmic.on.into()),
background: Some(Background::Color(cosmic.base.into())),
border_radius: 8.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}
use crate::widget::{Background, Color};
use iced::widget;
use iced_style::Theme;
pub use list_view;
pub use list_view_item;
pub use list_view_row;
pub use list_view_section;
}
pub mod list_box {
#[macro_export]
macro_rules! list_box_row {
($title:expr) => {
$crate::iced::widget::container(
$crate::iced::widget::row![
text($title).size(18),
$crate::iced::widget::vertical_space(Length::Fill),
$crate::iced::widget::horizontal_space(Length::Fill)
]
.height(Length::Fill)
.align_items($crate::iced::alignment::Alignment::Center),
)
.max_height(60)
.padding(10)
};
($title:expr, $subtitle:expr) => {
$crate::iced::widget::container(
$crate::iced::widget::row![
column(vec![
text($title).size(18).into(),
text($subtitle).size(16).into(),
]),
$crate::iced::widget::vertical_space(Length::Fill),
$crate::iced::widget::horizontal_space(Length::Fill)
]
.height(Length::Fill)
.align_items($crate::iced::alignment::Alignment::Center),
)
.max_height(60)
.padding(10)
};
($title:expr, $subtitle:expr, $icon:expr) => {
$crate::iced::widget::container(
$crate::iced::widget::row![
container($crate::widget::icon($icon, 20)).padding(10),
column(vec![
text($title).size(18).into(),
text($subtitle).size(16).into(),
]),
$crate::iced::widget::vertical_space(Length::Fill),
$crate::iced::widget::horizontal_space(Length::Fill)
]
.height(Length::Fill)
.align_items($crate::iced::alignment::Alignment::Center),
)
.max_height(60)
.padding(10)
};
}
pub use list_box_row;
}

View file

@ -1,8 +1,8 @@
pub mod macros; // Copyright 2022 System76 <info@system76.com>
pub use macros::*; // SPDX-License-Identifier: MPL-2.0
pub mod list_row; mod column;
pub use list_row::*; // mod item;
pub mod list_box; pub use self::column::{ListColumn, list_column};
pub use list_box::*; // pub use self::item::{ListItem, list_item};

View file

@ -1,26 +1,31 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
mod button; mod button;
pub use button::*; pub use button::*;
mod header_bar; mod header_bar;
pub use header_bar::*; pub use header_bar::{HeaderBar, header_bar};
mod icon; mod icon;
pub use self::icon::*; pub use self::icon::{Icon, icon};
pub mod list;
pub use self::list::*;
pub mod nav_button;
pub use self::nav_button::{NavButton, nav_button};
pub mod navigation; pub mod navigation;
pub use navigation::*; pub use navigation::*;
mod toggler; mod toggler;
pub use toggler::*; pub use toggler::toggler;
pub mod settings;
mod scrollable; mod scrollable;
pub use scrollable::*; pub use scrollable::*;
mod expander;
pub use expander::*;
pub mod list;
pub use list::*;
pub mod separator; pub mod separator;
pub use separator::*; pub use separator::{horizontal_rule, vertical_rule};

61
src/widget/nav_button.rs Normal file
View file

@ -0,0 +1,61 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use apply::Apply;
use derive_setters::Setters;
use iced::{alignment::Vertical, Length};
use crate::{Element, theme};
#[derive(Setters)]
pub struct NavButton<'a, Message> {
title: &'a str,
sidebar_active: bool,
#[setters(strip_option)]
on_sidebar_toggled: Option<Message>,
}
#[must_use]
pub fn nav_button<Message>(title: &str) -> NavButton<Message> {
NavButton {
title,
sidebar_active: false,
on_sidebar_toggled: None,
}
}
impl<'a, Message: 'static + Clone> From<NavButton<'a, Message>> for Element<'a, Message> {
fn from(nav_button: NavButton<'a, Message>) -> Self {
let text = iced::widget::text(&nav_button.title)
.style(theme::Text::Accent)
.vertical_alignment(Vertical::Center)
.width(Length::Shrink)
.height(Length::Fill);
let icon = super::icon(
if nav_button.sidebar_active {
"go-previous-symbolic"
} else {
"go-next-symbolic"
},
24,
)
.style(theme::Svg::SymbolicActive)
.width(Length::Units(24))
.height(Length::Fill);
let mut widget = iced::widget::row!(text, crate::widget::vertical_rule(4), icon)
.padding(4)
.spacing(4)
.apply(iced::widget::button)
.style(theme::Button::Secondary);
if let Some(message) = nav_button.on_sidebar_toggled.clone() {
widget = widget.on_press(message);
}
widget.apply(iced::widget::container)
.center_y()
.height(Length::Fill)
.into()
}
}

View file

@ -1,5 +1,9 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
pub mod nav_bar { pub mod nav_bar {
use iced::{widget, Background, Color, Theme}; use iced::{widget, Background, Color};
use crate::Theme;
#[macro_export] #[macro_export]
macro_rules! nav_button { macro_rules! nav_button {

View file

@ -1,3 +1,6 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
pub mod navbar; pub mod navbar;
pub use navbar::*; pub use navbar::*;

View file

@ -1,8 +1,10 @@
use crate::scrollable; // Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use crate::widget::nav_bar::{nav_bar_pages_style, nav_bar_sections_style}; use crate::widget::nav_bar::{nav_bar_pages_style, nav_bar_sections_style};
use crate::widget::Background; use crate::widget::Background;
use derive_setters::Setters; use derive_setters::Setters;
use iced::Length; use iced::{Background, Length};
use iced_lazy::Component; use iced_lazy::Component;
use iced_native::widget::{column, container}; use iced_native::widget::{column, container};
use iced_native::{row, Alignment, Element}; use iced_native::{row, Alignment, Element};
@ -45,10 +47,7 @@ pub struct NavBarItem {
impl NavBarItem { impl NavBarItem {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self::default()
title: String::new(),
icon: String::new(),
}
} }
} }
@ -69,8 +68,7 @@ pub struct NavBarState {
selected_page: Option<usize>, selected_page: Option<usize>,
} }
impl<'a, Message> Component<Message, Renderer> for NavBar<'a, Message> impl<'a, Message> Component<Message, Renderer> for NavBar<'a, Message> {
{
type State = NavBarState; type State = NavBarState;
type Event = NavBarEvent; type Event = NavBarEvent;
@ -96,8 +94,8 @@ impl<'a, Message> Component<Message, Renderer> for NavBar<'a, Message>
fn view(&self, state: &Self::State) -> Element<Self::Event, Renderer> { fn view(&self, state: &Self::State) -> Element<Self::Event, Renderer> {
if self.active { if self.active {
let mut sections: Vec<Element<Self::Event, Renderer>> = vec![]; let mut sections: Vec<Element<'a, Self::Event, Renderer>> = vec![];
let mut pages: Vec<Element<Self::Event, Renderer>> = vec![]; let mut pages: Vec<Element<'a, Self::Event, Renderer>> = vec![];
for (section_index, (section, section_pages)) in self.source.iter().enumerate() { for (section_index, (section, section_pages)) in self.source.iter().enumerate() {
sections.push( sections.push(
@ -122,23 +120,44 @@ impl<'a, Message> Component<Message, Renderer> for NavBar<'a, Message>
} }
} }
} }
let width: u16 = if self.condensed { 100 } else { 200 };
let nav_bar: Element<Self::Event, Renderer> = container(row![ let nav_bar: Element<Self::Event, Renderer> =
scrollable!(column(sections) container(if self.condensed && state.selected_page.is_some() {
row![container(scrollable(column(pages)
.spacing(10)
.padding(10)
.max_width(200)
.width(Length::Units(200))
.height(Length::Shrink)))
.height(Length::Fill)
.style(theme::Container::Custom(nav_bar_pages_style))]
} else if !state.section_active || self.condensed && state.selected_page.is_none() {
row![scrollable(column(sections)
.spacing(10)
.padding(10)
.max_width(100)
.align_items(Alignment::Center)
.height(Length::Shrink))]
} else {
row![
scrollable(column(sections)
.spacing(10) .spacing(10)
.padding(10) .padding(10)
.max_width(100) .max_width(100)
.align_items(Alignment::Center) .align_items(Alignment::Center)
.height(Length::Shrink)), .height(Length::Shrink)),
container(scrollable!(column(pages) container(scrollable(column(pages)
.spacing(10) .spacing(10)
.padding(10) .padding(10)
.max_width(width.into()) .max_width(200)
.align_items(Alignment::Center) .width(Length::Units(200))
.width(Length::Units(width))
.height(Length::Shrink))) .height(Length::Shrink)))
.height(Length::Fill) .height(Length::Fill)
.style(theme::Container::Custom(nav_bar_pages_style)), .style(theme::Container::Custom(nav_bar_pages_style)),
]
})
.height(Length::Fill)
.style(theme::Container::Custom(nav_bar_pages_style)),
]) ])
.height(Length::Fill) .height(Length::Fill)
.style(theme::Container::Custom(nav_bar_sections_style)) .style(theme::Container::Custom(nav_bar_sections_style))
@ -150,7 +169,8 @@ impl<'a, Message> Component<Message, Renderer> for NavBar<'a, Message>
} }
} }
impl<'a, Message: 'a> From<NavBar<'a, Message>> for Element<'a, Message, Renderer> impl<'a, Message: 'static> From<NavBar<'a, Message>>
for Element<'a, Message, Renderer>
{ {
fn from(nav_bar: NavBar<'a, Message>) -> Self { fn from(nav_bar: NavBar<'a, Message>) -> Self {
iced_lazy::component(nav_bar) iced_lazy::component(nav_bar)

View file

@ -1,8 +1,11 @@
#[macro_export] // Copyright 2022 System76 <info@system76.com>
macro_rules! scrollable { // SPDX-License-Identifier: MPL-2.0
($x:expr) => {
$crate::iced::widget::scrollable($x) use crate::{Element, Renderer};
use iced::widget;
pub fn scrollable<'a, Message>(element: impl Into<Element<'a, Message>>) -> widget::Scrollable<'a, Message, Renderer> {
widget::scrollable(element)
.scrollbar_width(8) .scrollbar_width(8)
.scroller_width(8) .scroller_width(8)
};
} }

View file

@ -1,7 +1,25 @@
#[macro_export] // Copyright 2022 System76 <info@system76.com>
macro_rules! separator { // SPDX-License-Identifier: MPL-2.0
($size:expr) => {
$crate::iced::widget::horizontal_rule($size) use crate::iced::widget;
.style(theme::Rule::Custom($crate::widget::separator_style)) use crate::{theme, Renderer, Theme};
};
#[must_use]
pub fn horizontal_rule(size: u16) -> widget::Rule<Renderer> {
widget::horizontal_rule(size).style(theme::Rule::Custom(separator_style))
}
#[must_use]
pub fn vertical_rule(size: u16) -> widget::Rule<Renderer> {
widget::vertical_rule(size).style(theme::Rule::Custom(separator_style))
}
fn separator_style(theme: &Theme) -> widget::rule::Appearance {
let cosmic = &theme.cosmic().primary;
widget::rule::Appearance {
color: cosmic.divider.into(),
width: 1,
radius: 0.0,
fill_mode: widget::rule::FillMode::Padded(10),
}
} }

View file

@ -0,0 +1,26 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use crate::{Element, Renderer};
/// A setting within a settings view section.
#[must_use]
#[allow(clippy::module_name_repetitions)]
pub fn item<'a, Message: 'static>(title: &'a str, widget: impl Into<Element<'a, Message>>) -> iced::widget::Row<'a, Message, Renderer> {
item_row(vec![
iced::widget::text(title).into(),
iced::widget::horizontal_space(iced::Length::Fill).into(),
widget.into()
])
}
/// A settings item aligned in a row
#[must_use]
#[allow(clippy::module_name_repetitions)]
pub fn item_row<Message>(children: Vec<Element<Message>>) -> iced::widget::Row<Message, Renderer> {
iced::widget::row(children)
.align_items(iced::Alignment::Center)
.padding([0, 8])
.spacing(12)
}

View file

@ -0,0 +1,17 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
mod item;
mod section;
pub use self::item::{item, item_row};
pub use self::section::{Section, view_section};
use crate::{Element, Renderer};
use iced::widget::{Column, column};
/// A column with a predefined style for creating a settings panel
#[must_use]
pub fn view_column<Message: 'static>(children: Vec<Element<Message>>) -> Column<Message, Renderer> {
column(children).spacing(24).padding(24).max_width(600)
}

View file

@ -0,0 +1,39 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use crate::{Element};
use crate::widget::{ListColumn};
use iced::widget::{column, text};
/// A section within a settings view column.
#[must_use]
pub fn view_section<Message: 'static>(
title: &str,
) -> Section<Message> {
Section { title, children: ListColumn::default() }
}
pub struct Section<'a, Message> {
title: &'a str,
children: ListColumn<'a, Message>
}
impl<'a, Message: 'static> Section<'a, Message> {
#[must_use]
pub fn add(mut self, item: impl Into<Element<'a, Message>>) -> Self {
self.children = self.children.add(item.into());
self
}
}
impl<'a, Message: 'static> From<Section<'a, Message>> for Element<'a, Message> {
fn from(data: Section<'a, Message>) -> Self {
let title = text(data.title)
.font(crate::font::FONT_SEMIBOLD)
.into();
column(vec![title, data.children.into_element()])
.spacing(8)
.into()
}
}

View file

@ -1,14 +1,14 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use crate::Renderer;
use iced::{widget, Length}; use iced::{widget, Length};
pub fn toggler<'a, Message, Renderer>( pub fn toggler<'a, Message>(
label: impl Into<Option<String>>, label: impl Into<Option<String>>,
is_checked: bool, is_checked: bool,
f: impl Fn(bool) -> Message + 'a, f: impl Fn(bool) -> Message + 'a,
) -> widget::Toggler<'a, Message, Renderer> ) -> widget::Toggler<'a, Message, Renderer> {
where
Renderer: iced_native::text::Renderer,
Renderer::Theme: widget::toggler::StyleSheet,
{
widget::Toggler::new(is_checked, label, f) widget::Toggler::new(is_checked, label, f)
.size(24) .size(24)
.spacing(12) .spacing(12)

View file

@ -1 +0,0 @@
RUST_LOG=text=info cargo run --release --package text -- "$@"