mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Wrap outline entry body in LRE/RLE + make smart quotes ignore directional control characters (#4491)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
a3f3a1a833
commit
17ee3df1ba
@ -201,7 +201,7 @@ pub fn collect<'a>(
|
|||||||
);
|
);
|
||||||
let peeked = iter.peek().and_then(|(child, _)| {
|
let peeked = iter.peek().and_then(|(child, _)| {
|
||||||
if let Some(elem) = child.to_packed::<TextElem>() {
|
if let Some(elem) = child.to_packed::<TextElem>() {
|
||||||
elem.text().chars().next()
|
elem.text().chars().find(|c| !is_default_ignorable(*c))
|
||||||
} else if child.is::<SmartQuoteElem>() {
|
} else if child.is::<SmartQuoteElem>() {
|
||||||
Some('"')
|
Some('"')
|
||||||
} else if child.is::<SpaceElem>()
|
} else if child.is::<SpaceElem>()
|
||||||
@ -302,7 +302,7 @@ impl<'a> Collector<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn push_segment(&mut self, segment: Segment<'a>, is_quote: bool) {
|
fn push_segment(&mut self, segment: Segment<'a>, is_quote: bool) {
|
||||||
if let Some(last) = self.full.chars().last() {
|
if let Some(last) = self.full.chars().rev().find(|c| !is_default_ignorable(*c)) {
|
||||||
self.quoter.last(last, is_quote);
|
self.quoter.last(last, is_quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -953,3 +953,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether a codepoint is Unicode `Default_Ignorable`.
|
||||||
|
pub fn is_default_ignorable(c: char) -> bool {
|
||||||
|
DEFAULT_IGNORABLE_DATA.as_borrowed().contains(c)
|
||||||
|
}
|
||||||
|
@ -10,7 +10,7 @@ use comemo::{Track, Tracked, TrackedMut};
|
|||||||
use self::collect::{collect, Item, Segment, SpanMapper};
|
use self::collect::{collect, Item, Segment, SpanMapper};
|
||||||
use self::finalize::finalize;
|
use self::finalize::finalize;
|
||||||
use self::line::{commit, line, Line};
|
use self::line::{commit, line, Line};
|
||||||
use self::linebreak::{linebreak, Breakpoint};
|
use self::linebreak::{is_default_ignorable, linebreak, Breakpoint};
|
||||||
use self::prepare::{prepare, Preparation};
|
use self::prepare::{prepare, Preparation};
|
||||||
use self::shaping::{
|
use self::shaping::{
|
||||||
cjk_punct_style, is_of_cj_script, shape_range, ShapedGlyph, ShapedText,
|
cjk_punct_style, is_of_cj_script, shape_range, ShapedGlyph, ShapedText,
|
||||||
|
@ -483,7 +483,7 @@ impl OutlineEntry {
|
|||||||
|
|
||||||
impl Show for Packed<OutlineEntry> {
|
impl Show for Packed<OutlineEntry> {
|
||||||
#[typst_macros::time(name = "outline.entry", span = self.span())]
|
#[typst_macros::time(name = "outline.entry", span = self.span())]
|
||||||
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
||||||
let mut seq = vec![];
|
let mut seq = vec![];
|
||||||
let elem = self.element();
|
let elem = self.element();
|
||||||
|
|
||||||
@ -500,7 +500,11 @@ impl Show for Packed<OutlineEntry> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The body text remains overridable.
|
// The body text remains overridable.
|
||||||
seq.push(self.body().clone().linked(Destination::Location(location)));
|
crate::text::isolate(
|
||||||
|
self.body().clone().linked(Destination::Location(location)),
|
||||||
|
styles,
|
||||||
|
&mut seq,
|
||||||
|
);
|
||||||
|
|
||||||
// Add filler symbols between the section name and page number.
|
// Add filler symbols between the section name and page number.
|
||||||
if let Some(filler) = self.fill() {
|
if let Some(filler) = self.fill() {
|
||||||
|
@ -1299,3 +1299,13 @@ cast! {
|
|||||||
ret
|
ret
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pushes `text` wrapped in LRE/RLE + PDF to `out`.
|
||||||
|
pub(crate) fn isolate(text: Content, styles: StyleChain, out: &mut Vec<Content>) {
|
||||||
|
out.push(TextElem::packed(match TextElem::dir_in(styles) {
|
||||||
|
Dir::RTL => "\u{202B}",
|
||||||
|
_ => "\u{202A}",
|
||||||
|
}));
|
||||||
|
out.push(text);
|
||||||
|
out.push(TextElem::packed("\u{202C}"));
|
||||||
|
}
|
||||||
|
@ -123,7 +123,7 @@ impl SmartQuoter {
|
|||||||
|
|
||||||
/// Process the last seen character.
|
/// Process the last seen character.
|
||||||
pub fn last(&mut self, c: char, is_quote: bool) {
|
pub fn last(&mut self, c: char, is_quote: bool) {
|
||||||
self.expect_opening = is_ignorable(c) || is_opening_bracket(c);
|
self.expect_opening = is_exterior_to_quote(c) || is_opening_bracket(c);
|
||||||
self.last_num = c.is_numeric();
|
self.last_num = c.is_numeric();
|
||||||
if !is_quote {
|
if !is_quote {
|
||||||
self.prev_quote_type = None;
|
self.prev_quote_type = None;
|
||||||
@ -150,7 +150,7 @@ impl SmartQuoter {
|
|||||||
self.prev_quote_type = Some(double);
|
self.prev_quote_type = Some(double);
|
||||||
quotes.open(double)
|
quotes.open(double)
|
||||||
} else if self.quote_depth > 0
|
} else if self.quote_depth > 0
|
||||||
&& (peeked.is_ascii_punctuation() || is_ignorable(peeked))
|
&& (peeked.is_ascii_punctuation() || is_exterior_to_quote(peeked))
|
||||||
{
|
{
|
||||||
self.quote_depth -= 1;
|
self.quote_depth -= 1;
|
||||||
quotes.close(double)
|
quotes.close(double)
|
||||||
@ -168,7 +168,7 @@ impl Default for SmartQuoter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_ignorable(c: char) -> bool {
|
fn is_exterior_to_quote(c: char) -> bool {
|
||||||
c.is_whitespace() || is_newline(c)
|
c.is_whitespace() || is_newline(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
tests/ref/issue-4476-rtl-title-ending-in-ltr-text.png
Normal file
BIN
tests/ref/issue-4476-rtl-title-ending-in-ltr-text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
BIN
tests/ref/smartquote-with-embedding-chars.png
Normal file
BIN
tests/ref/smartquote-with-embedding-chars.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 571 B |
@ -176,3 +176,10 @@ Ok ...
|
|||||||
// Error: 2-55 heading must have a location
|
// Error: 2-55 heading must have a location
|
||||||
// Hint: 2-55 try using a query or a show rule to customize the outline.entry instead
|
// Hint: 2-55 try using a query or a show rule to customize the outline.entry instead
|
||||||
#outline.entry(1, heading[Hello], [World!], none, [1])
|
#outline.entry(1, heading[Hello], [World!], none, [1])
|
||||||
|
|
||||||
|
--- issue-4476-rtl-title-ending-in-ltr-text ---
|
||||||
|
#set text(lang: "he")
|
||||||
|
#outline()
|
||||||
|
|
||||||
|
= הוקוס Pocus
|
||||||
|
= זוהי כותרת שתורגמה על ידי מחשב
|
||||||
|
@ -69,6 +69,11 @@ Some people's thought on this would be #[#set smartquote(enabled: false); "stran
|
|||||||
"'test' statement" \
|
"'test' statement" \
|
||||||
"statement 'test'"
|
"statement 'test'"
|
||||||
|
|
||||||
|
--- smartquote-with-embedding-chars ---
|
||||||
|
#set text(lang: "fr")
|
||||||
|
"#"\u{202A}"bonjour#"\u{202C}"" \
|
||||||
|
#"\u{202A}""bonjour"#"\u{202C}"
|
||||||
|
|
||||||
--- smartquote-custom ---
|
--- smartquote-custom ---
|
||||||
// Use language quotes for missing keys, allow partial reset
|
// Use language quotes for missing keys, allow partial reset
|
||||||
#set smartquote(quotes: "«»")
|
#set smartquote(quotes: "«»")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user