mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Make language non-optional with english as default
This commit is contained in:
parent
67e9313b91
commit
d025854457
39
src/library/text/lang.rs
Normal file
39
src/library/text/lang.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use crate::eval::Value;
|
||||||
|
use crate::geom::Dir;
|
||||||
|
|
||||||
|
/// A natural language.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct Lang([u8; 2]);
|
||||||
|
|
||||||
|
impl Lang {
|
||||||
|
/// The code for the english language.
|
||||||
|
pub const ENGLISH: Self = Self(*b"en");
|
||||||
|
|
||||||
|
/// Construct a language from a two-byte ISO 639-1 code.
|
||||||
|
pub fn from_str(iso: &str) -> Option<Self> {
|
||||||
|
let mut bytes: [u8; 2] = iso.as_bytes().try_into().ok()?;
|
||||||
|
bytes.make_ascii_lowercase();
|
||||||
|
Some(Self(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the language code as a string slice.
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
std::str::from_utf8(&self.0).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The default direction for the language.
|
||||||
|
pub fn dir(&self) -> Dir {
|
||||||
|
match self.as_str() {
|
||||||
|
"ar" | "dv" | "fa" | "he" | "ks" | "pa" | "ps" | "sd" | "ug" | "ur"
|
||||||
|
| "yi" => Dir::RTL,
|
||||||
|
_ => Dir::LTR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
Lang,
|
||||||
|
Expected: "string",
|
||||||
|
Value::Str(string) => Self::from_str(&string)
|
||||||
|
.ok_or("expected two letter language code")?,
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
//! Text handling and paragraph layout.
|
//! Text handling and paragraph layout.
|
||||||
|
|
||||||
mod deco;
|
mod deco;
|
||||||
|
mod lang;
|
||||||
mod link;
|
mod link;
|
||||||
mod par;
|
mod par;
|
||||||
mod quotes;
|
mod quotes;
|
||||||
@ -8,6 +9,7 @@ mod raw;
|
|||||||
mod shaping;
|
mod shaping;
|
||||||
|
|
||||||
pub use deco::*;
|
pub use deco::*;
|
||||||
|
pub use lang::*;
|
||||||
pub use link::*;
|
pub use link::*;
|
||||||
pub use par::*;
|
pub use par::*;
|
||||||
pub use quotes::*;
|
pub use quotes::*;
|
||||||
@ -64,8 +66,7 @@ impl TextNode {
|
|||||||
pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline);
|
pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline);
|
||||||
|
|
||||||
/// An ISO 639-1 language code.
|
/// An ISO 639-1 language code.
|
||||||
#[property(referenced)]
|
pub const LANG: Lang = Lang::ENGLISH;
|
||||||
pub const LANG: Option<Lang> = None;
|
|
||||||
/// The direction for text and inline objects. When `auto`, the direction is
|
/// The direction for text and inline objects. When `auto`, the direction is
|
||||||
/// automatically inferred from the language.
|
/// automatically inferred from the language.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
@ -257,32 +258,6 @@ castable! {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A natural language.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct Lang(EcoString);
|
|
||||||
|
|
||||||
impl Lang {
|
|
||||||
/// The default direction for the language.
|
|
||||||
pub fn dir(&self) -> Dir {
|
|
||||||
match self.0.as_str() {
|
|
||||||
"ar" | "dv" | "fa" | "he" | "ks" | "pa" | "ps" | "sd" | "ug" | "ur"
|
|
||||||
| "yi" => Dir::RTL,
|
|
||||||
_ => Dir::LTR,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the language code as a string slice.
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Lang,
|
|
||||||
Expected: "string",
|
|
||||||
Value::Str(string) => Self(string.to_lowercase()),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The direction of text and inline objects in their line.
|
/// The direction of text and inline objects in their line.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct HorizontalDir(pub Dir);
|
pub struct HorizontalDir(pub Dir);
|
||||||
@ -301,10 +276,7 @@ impl Resolve for Smart<HorizontalDir> {
|
|||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
match self {
|
match self {
|
||||||
Smart::Auto => match styles.get(TextNode::LANG) {
|
Smart::Auto => styles.get(TextNode::LANG).dir(),
|
||||||
Some(lang) => lang.dir(),
|
|
||||||
None => Dir::LTR,
|
|
||||||
},
|
|
||||||
Smart::Custom(dir) => dir.0,
|
Smart::Custom(dir) => dir.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -408,11 +408,7 @@ fn collect<'a>(
|
|||||||
if styles.get(TextNode::SMART_QUOTES) {
|
if styles.get(TextNode::SMART_QUOTES) {
|
||||||
// TODO: Also get region.
|
// TODO: Also get region.
|
||||||
let lang = styles.get(TextNode::LANG);
|
let lang = styles.get(TextNode::LANG);
|
||||||
let quotes = lang
|
let quotes = Quotes::from_lang(lang.as_str(), "");
|
||||||
.as_ref()
|
|
||||||
.map(|lang| Quotes::from_lang(lang.as_str(), ""))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let peeked = iter.peek().and_then(|(child, _)| match child {
|
let peeked = iter.peek().and_then(|(child, _)| match child {
|
||||||
ParChild::Text(text) => text.chars().next(),
|
ParChild::Text(text) => text.chars().next(),
|
||||||
ParChild::Quote(_) => Some('"'),
|
ParChild::Quote(_) => Some('"'),
|
||||||
@ -750,7 +746,7 @@ fn breakpoints<'a>(p: &'a Preparation) -> Breakpoints<'a> {
|
|||||||
end: 0,
|
end: 0,
|
||||||
mandatory: false,
|
mandatory: false,
|
||||||
hyphenate: p.get_shared(TextNode::HYPHENATE),
|
hyphenate: p.get_shared(TextNode::HYPHENATE),
|
||||||
lang: p.get_shared(TextNode::LANG).map(Option::as_ref),
|
lang: p.get_shared(TextNode::LANG),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -773,7 +769,7 @@ struct Breakpoints<'a> {
|
|||||||
/// Whether to hyphenate if it's the same for all children.
|
/// Whether to hyphenate if it's the same for all children.
|
||||||
hyphenate: Option<bool>,
|
hyphenate: Option<bool>,
|
||||||
/// The text language if it's the same for all children.
|
/// The text language if it's the same for all children.
|
||||||
lang: Option<Option<&'a Lang>>,
|
lang: Option<Lang>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for Breakpoints<'_> {
|
impl Iterator for Breakpoints<'_> {
|
||||||
@ -831,9 +827,9 @@ impl Breakpoints<'_> {
|
|||||||
|
|
||||||
/// The text language at the given offset.
|
/// The text language at the given offset.
|
||||||
fn lang_at(&self, offset: usize) -> Option<hypher::Lang> {
|
fn lang_at(&self, offset: usize) -> Option<hypher::Lang> {
|
||||||
let lang = self.lang.unwrap_or_else(|| {
|
let lang = self.lang.or_else(|| {
|
||||||
let shaped = self.p.find(offset)?.text()?;
|
let shaped = self.p.find(offset)?.text()?;
|
||||||
shaped.styles.get(TextNode::LANG).as_ref()
|
Some(shaped.styles.get(TextNode::LANG))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let bytes = lang.as_str().as_bytes().try_into().ok()?;
|
let bytes = lang.as_str().as_bytes().try_into().ok()?;
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@ -6,13 +6,13 @@
|
|||||||
#set page(width: auto)
|
#set page(width: auto)
|
||||||
#grid(
|
#grid(
|
||||||
columns: (70pt, 60pt),
|
columns: (70pt, 60pt),
|
||||||
text(lang: "en")[Warm welcomes to Typst.],
|
[Warm welcomes to Typst.],
|
||||||
text(lang: "el")[διαμερίσματα. \ λατρευτός],
|
text(lang: "el")[διαμερίσματα. \ λατρευτός],
|
||||||
)
|
)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test disabling hyphenation for short passages.
|
// Test disabling hyphenation for short passages.
|
||||||
#set text(lang: "en", hyphenate: true)
|
#set text(hyphenate: true)
|
||||||
|
|
||||||
Welcome to wonderful experiences. \
|
Welcome to wonderful experiences. \
|
||||||
Welcome to `wonderful` experiences. \
|
Welcome to `wonderful` experiences. \
|
||||||
@ -20,14 +20,14 @@ Welcome to #text(hyphenate: false)[wonderful] experiences. \
|
|||||||
Welcome to wonde#text(hyphenate: false)[rf]ul experiences. \
|
Welcome to wonde#text(hyphenate: false)[rf]ul experiences. \
|
||||||
|
|
||||||
// Test enabling hyphenation for short passages.
|
// Test enabling hyphenation for short passages.
|
||||||
#set text(lang: "en", hyphenate: false)
|
#set text(hyphenate: false)
|
||||||
Welcome to wonderful experiences. \
|
Welcome to wonderful experiences. \
|
||||||
Welcome to wo#text(hyphenate: true)[nd]erful experiences. \
|
Welcome to wo#text(hyphenate: true)[nd]erful experiences. \
|
||||||
|
|
||||||
---
|
---
|
||||||
// Hyphenate between shape runs.
|
// Hyphenate between shape runs.
|
||||||
#set page(width: 80pt)
|
#set page(width: 80pt)
|
||||||
#set text(lang: "en", hyphenate: true)
|
#set text(hyphenate: true)
|
||||||
It's a #emph[Tree]beard.
|
It's a #emph[Tree]beard.
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -46,5 +46,5 @@ It's a #emph[Tree]beard.
|
|||||||
// do that. The test passes if there's just one hyphenation between
|
// do that. The test passes if there's just one hyphenation between
|
||||||
// "net" and "works".
|
// "net" and "works".
|
||||||
#set page(width: 70pt)
|
#set page(width: 70pt)
|
||||||
#set text(lang: "en", hyphenate: true)
|
#set text(hyphenate: true)
|
||||||
#h(6pt) networks, the rest.
|
#h(6pt) networks, the rest.
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
#set page(width: 180pt)
|
#set page(width: 180pt)
|
||||||
#set text(lang: "en")
|
|
||||||
#set par(
|
#set par(
|
||||||
justify: true,
|
justify: true,
|
||||||
indent: 14pt,
|
indent: 14pt,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#set page(width: auto, height: auto)
|
#set page(width: auto, height: auto)
|
||||||
#set par(leading: 4pt, justify: true)
|
#set par(leading: 4pt, justify: true)
|
||||||
#set text(lang: "en", family: "Latin Modern Roman")
|
#set text(family: "Latin Modern Roman")
|
||||||
|
|
||||||
#let story = [
|
#let story = [
|
||||||
In olden times when wishing still helped one, there lived a king whose
|
In olden times when wishing still helped one, there lived a king whose
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
// Test hanging punctuation.
|
// Test hanging punctuation.
|
||||||
#set page(width: 130pt, margins: 15pt)
|
#set page(width: 130pt, margins: 15pt)
|
||||||
#set par(justify: true, linebreaks: "simple")
|
#set par(justify: true, linebreaks: "simple")
|
||||||
#set text(lang: "en", size: 9pt)
|
#set text(size: 9pt)
|
||||||
#rect(fill: rgb(repr(teal) + "00"), width: 100%)[
|
#rect(fill: rgb(repr(teal) + "00"), width: 100%)[
|
||||||
This is a little bit of text that builds up to
|
This is a little bit of text that builds up to
|
||||||
hang-ing hyphens and dash---es and then, you know,
|
hang-ing hyphens and dash---es and then, you know,
|
||||||
|
@ -24,12 +24,10 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test single pair of quotes.
|
// Test single pair of quotes.
|
||||||
#set text(lang: "en")
|
|
||||||
""
|
""
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test sentences with numbers and apostrophes.
|
// Test sentences with numbers and apostrophes.
|
||||||
#set text(lang: "en")
|
|
||||||
The 5'11" 'quick' brown fox jumps over the "lazy" dog's ear.
|
The 5'11" 'quick' brown fox jumps over the "lazy" dog's ear.
|
||||||
|
|
||||||
He said "I'm a big fella."
|
He said "I'm a big fella."
|
||||||
@ -40,7 +38,6 @@ The 5\'11\" 'quick\' brown fox jumps over the \"lazy" dog\'s ear.
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test turning smart quotes off.
|
// Test turning smart quotes off.
|
||||||
#set text(lang: "en")
|
|
||||||
He's told some books contain questionable "example text".
|
He's told some books contain questionable "example text".
|
||||||
|
|
||||||
#set text(smart-quotes: false)
|
#set text(smart-quotes: false)
|
||||||
@ -48,7 +45,6 @@ He's told some books contain questionable "example text".
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test changing properties within text.
|
// Test changing properties within text.
|
||||||
#set text(lang: "en")
|
|
||||||
"She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me.
|
"She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me.
|
||||||
|
|
||||||
Some people's thought on this would be #text(smart-quotes: false)["strange."]
|
Some people's thought on this would be #text(smart-quotes: false)["strange."]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user