tectonic_xetex_layout 0.3.3

XeTeX's font loading and layout interface encapsulation, as a crate.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
//! Base engine for laying out fonts.

use crate::font::Font;
use crate::manager::{Engine, FontManager};
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::ops::{Deref, DerefMut};
use std::ptr;
use tectonic_bridge_graphite2 as gr;
use tectonic_bridge_harfbuzz as hb;

/// Item that may be borrowed or owned. Similar to `Cow`, but with mutable references.
pub enum MaybeBorrow<'a, T> {
    /// Owned item
    Owned(Box<T>),
    /// Borrowed item
    Borrowed(&'a mut T),
}

impl<T> Deref for MaybeBorrow<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        match self {
            MaybeBorrow::Owned(val) => val,
            MaybeBorrow::Borrowed(val) => val,
        }
    }
}

impl<T> DerefMut for MaybeBorrow<'_, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        match self {
            MaybeBorrow::Owned(val) => val,
            MaybeBorrow::Borrowed(val) => val,
        }
    }
}

pub(crate) struct GrBreak {
    pub(crate) segment: gr::Segment,
    pub(crate) slot: gr::Slot,
    pub(crate) text_len: libc::c_uint,
}

/// Base layout engine. Combines all the information needed to shape a font for TeX.
#[repr(C)]
pub struct LayoutEngine {
    font: MaybeBorrow<'static, Font>,
    script: hb::Tag,
    pub(crate) language: hb::Language,
    pub(crate) features: Box<[hb::Feature]>,
    /// the requested shapers
    shaper_list: Vec<*const libc::c_char>,
    /// the actually used shaper
    shaper: Option<CString>,
    rgb_value: u32,
    extend: f32,
    slant: f32,
    embolden: f32,
    hb_buffer: hb::Buffer,
    pub(crate) gr_breaking: Option<GrBreak>,
}

impl LayoutEngine {
    /// Create a new layout engine from the needed information
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        font: MaybeBorrow<'static, Font>,
        script: hb::Tag,
        language: Option<Cow<'static, CStr>>,
        features: Box<[hb::Feature]>,
        shaper_list: Vec<*const libc::c_char>,
        rgb_value: u32,
        extend: f32,
        slant: f32,
        embolden: f32,
    ) -> LayoutEngine {
        let req_engine = FontManager::with_font_manager(|mgr| mgr.get_req_engine());
        LayoutEngine {
            font,
            script,
            // For Graphite fonts treat the language as BCP 47 tag, for OpenType we
            // treat it as a OT language tag for backward compatibility with pre-0.9999
            // XeTeX.
            language: if req_engine == Engine::Graphite {
                language
                    .map(|lang| hb::Language::from_cstr(&lang))
                    .unwrap_or_default()
            } else {
                language
                    .map(|lang| hb::Tag::from_cstr(&lang).to_language())
                    .unwrap_or_default()
            },
            features,
            shaper_list,
            shaper: None,
            rgb_value,
            extend,
            slant,
            embolden,
            hb_buffer: hb::Buffer::new(),
            gr_breaking: None,
        }
    }

    /// Get the font script used
    pub fn script(&self) -> hb::Tag {
        self.script
    }

    /// Get the extend
    pub fn extend(&self) -> f32 {
        self.extend
    }

    /// Get the slant
    pub fn slant(&self) -> f32 {
        self.slant
    }

    /// Get the embolden
    pub fn embolden(&self) -> f32 {
        self.embolden
    }

    /// Get the RGB
    pub fn rgb(&self) -> u32 {
        self.rgb_value
    }

    /// Get the font
    pub fn font(&self) -> &Font {
        &self.font
    }

    /// Get a mutable reference to the font
    pub fn font_mut(&mut self) -> &mut Font {
        &mut self.font
    }

    /// Get the default direction
    pub fn default_dir(&self) -> u8 {
        pub const UBIDI_DEFAULT_LTR: u8 = 0xFE;
        pub const UBIDI_DEFAULT_RTL: u8 = 0xFF;

        let script = self.hb_buffer.as_ref().get_script();
        if script.get_horizontal_direction() == hb::Direction::Rtl {
            UBIDI_DEFAULT_RTL
        } else {
            UBIDI_DEFAULT_LTR
        }
    }

    /// Check whether we're using the graphite shaper
    pub fn used_graphite(&self) -> bool {
        self.shaper
            .as_ref()
            .is_some_and(|s| s.to_bytes() == b"graphite2")
    }

    /// Check whether we're using the OpenType shaper
    pub fn used_ot(&self) -> bool {
        self.shaper.as_ref().is_some_and(|s| s.to_bytes() == b"ot")
    }

    /// Get the harfbuzz buffer
    pub fn hb_buffer(&self) -> hb::BufferRef<'_> {
        self.hb_buffer.as_ref()
    }

    /// Get the number of characters in the given slice
    pub fn layout_chars(&mut self, chars: &[u16], rtl: bool) -> usize {
        let hb_font = self.font.hb_font();
        let hb_face = hb_font.face();

        let direction = if self.font.layout_dir_vertical() {
            hb::Direction::Ttb
        } else if rtl {
            hb::Direction::Rtl
        } else {
            hb::Direction::Ltr
        };

        let script = self.script.to_script();
        self.hb_buffer.as_mut().reset();
        // TODO: figure out cfg for harfbuzz versions below 2.5
        // if hb_version_atleast(2, 5, 0) == 0 {
        //     #[derive(Copy, Clone)]
        //     struct SendSync(*mut hb_unicode_funcs_t);
        //
        //     unsafe impl Send for SendSync {}
        //     unsafe impl Sync for SendSync {}
        //
        //     unsafe extern "C" fn _decompose_compat(
        //         _: *mut hb_unicode_funcs_t,
        //         _: hb_codepoint_t,
        //         _: *mut hb_codepoint_t,
        //         _: *mut libc::c_void,
        //     ) -> libc::c_uint {
        //         0
        //     }
        //
        //     unsafe extern "C" fn _get_unicode_funcs() -> *mut hb_unicode_funcs_t {
        //         static UFUNCS: OnceLock<SendSync> = OnceLock::new();
        //         let ufuncs = *UFUNCS
        //             .get_or_init(|| SendSync(hb_unicode_funcs_create(hb_icu_get_unicode_funcs())));
        //
        //         hb_unicode_funcs_set_decompose_compatibility_func(
        //             ufuncs.0,
        //             _decompose_compat,
        //             ptr::null_mut(),
        //             None,
        //         );
        //
        //         ufuncs.0
        //     }
        //
        //     static HB_UNICODE_FUNCS: OnceLock<SendSync> = OnceLock::new();
        //     let funcs = *HB_UNICODE_FUNCS.get_or_init(|| SendSync(_get_unicode_funcs()));
        //     hb_buffer_set_unicode_funcs(engine.hb_buffer, funcs.0);
        // }

        self.hb_buffer.as_mut().add_utf16(chars);
        self.hb_buffer.as_mut().set_direction(direction);
        self.hb_buffer.as_mut().set_script(script);
        self.hb_buffer.as_mut().set_language(self.language);

        self.hb_buffer.as_mut().guess_segment_properties();
        let segment_props = self.hb_buffer.as_mut().get_segment_properties();

        if self.shaper_list.is_empty() {
            // HarfBuzz gives graphite2 shaper a priority, so that for hybrid
            // Graphite/OpenType fonts, Graphite will be used. However, pre-0.9999
            // XeTeX preferred OpenType over Graphite, so we are doing the same
            // here for sake of backward compatibility. Since "ot" shaper never
            // fails, we set the shaper list to just include it.
            self.shaper_list = vec![c"ot".as_ptr(), ptr::null()];
        }

        let mut shape_plan = hb::ShapePlan::new_cached(
            hb_face,
            &segment_props,
            &self.features,
            Some(&self.shaper_list),
        );
        let res = shape_plan
            .as_mut()
            .execute(hb_font, self.hb_buffer.as_mut(), &self.features);

        self.shaper = None;

        if res {
            self.shaper = shape_plan.as_ref().get_shaper().map(CStr::to_owned);
        } else {
            // all selected shapers failed, retrying with default
            // we don't use _cached here as the cached plan will always fail.
            shape_plan = hb::ShapePlan::new(hb_face, &segment_props, &self.features, None);
            let res = shape_plan
                .as_mut()
                .execute(hb_font, self.hb_buffer.as_mut(), &self.features);

            if res {
                self.shaper = shape_plan.as_ref().get_shaper().map(CStr::to_owned);
            } else {
                panic!("all shapers failed");
            }
        }

        self.hb_buffer.as_ref().len()
    }
}

pub(crate) fn get_graphite_feature_code(engine: &LayoutEngine, index: u32) -> Option<u32> {
    let id = engine
        .font()
        .hb_font()
        .face()
        .gr_face()?
        .feature_ref(index as usize)?
        .id();
    Some(id)
}

pub(crate) fn count_graphite_feature_settings(
    engine: &LayoutEngine,
    feature_id: u32,
) -> Option<u32> {
    let out = engine
        .font()
        .hb_font()
        .face()
        .gr_face()?
        .find_feature_ref(feature_id)?
        .num_values() as u32;
    Some(out)
}

pub(crate) fn get_graphite_feature_setting_code(
    engine: &LayoutEngine,
    feature_id: u32,
    index: u32,
) -> Option<u32> {
    let out = engine
        .font()
        .hb_font()
        .face()
        .gr_face()?
        .find_feature_ref(feature_id)?
        .value(index as usize) as u32;
    Some(out)
}

pub(crate) fn get_graphite_feature_default_setting(
    engine: &LayoutEngine,
    feature_id: u32,
) -> Option<u32> {
    let face = engine.font().hb_font().face().gr_face()?;
    let feat = face.find_feature_ref(feature_id)?;
    let lang = engine
        .language
        .to_string()
        .map(hb::Tag::from_cstr)
        .map(hb::Tag::to_raw)
        .unwrap_or(0);
    let feature_values = face.feature_val_for_lang(lang);
    let out = feat.feat_value(feature_values.as_ref()) as u32;
    Some(out)
}

pub(crate) fn get_graphite_feature_label(
    engine: &LayoutEngine,
    feature_id: u32,
) -> Option<gr::Label> {
    let face = engine.font().hb_font().face().gr_face()?;
    let feature = face.find_feature_ref(feature_id)?;
    let lang_id = 0x409;
    let label = feature.label(lang_id)?;
    Some(label)
}

pub(crate) fn get_graphite_feature_setting_label(
    engine: &LayoutEngine,
    feature_id: u32,
    setting_id: u32,
) -> Option<gr::Label> {
    let face = engine.font().hb_font().face().gr_face()?;

    let feature = face.find_feature_ref(feature_id)?;
    for i in 0..feature.num_values() {
        if setting_id == feature.value(i) as u32 {
            let lang_id = 0x409;
            return feature.value_label(i, lang_id);
        }
    }

    None
}

pub(crate) fn find_graphite_feature(
    engine: &LayoutEngine,
    str: &[u8],
    tag: &mut hb::Tag,
    v: &mut libc::c_int,
) -> bool {
    *tag = hb::Tag::new(0);
    *v = 0;

    let mut idx = 0;
    while str[idx] == b' ' || str[idx] == b'\t' {
        idx += 1;
    }
    while str.get(idx).is_some_and(|c| *c != b'=') {
        idx += 1;
    }

    match find_graphite_feature_named(engine, &str[..idx]) {
        Some(val) => *tag = hb::Tag::new(val),
        None => return false,
    }

    idx += 1;
    while idx < str.len() && (str[idx] == b' ' || str[idx] == b'\t') {
        idx += 1;
    }

    if idx >= str.len() {
        return false;
    }

    *v = find_graphite_feature_setting_named(engine, tag.to_raw(), &str[idx..])
        .map(|i| i as libc::c_int)
        .unwrap_or(-1);

    *v != -1
}

pub(crate) fn find_graphite_feature_named(engine: &LayoutEngine, name: &[u8]) -> Option<u32> {
    let gr_face = engine.font().hb_font().face().gr_face()?;

    let tag = hb::Tag::from_str(std::str::from_utf8(name).unwrap()).to_raw();

    for i in 0..gr_face.num_feature_refs() {
        let feature = gr_face.feature_ref(i)?;
        let lang_id = 0x409;
        let label = feature.label(lang_id)?;

        if &label.as_bytes()[..name.len()] == name || feature.id() == tag {
            return Some(feature.id());
        }
    }

    None
}

pub(crate) fn find_graphite_feature_setting_named(
    engine: &LayoutEngine,
    id: u32,
    name: &[u8],
) -> Option<i16> {
    let face = engine.font().hb_font().face().gr_face()?;

    let tag = hb::Tag::from_str(std::str::from_utf8(name).unwrap()).to_raw();

    let feature = face.find_feature_ref(id)?;
    for i in 0..feature.num_values() {
        let lang_id = 0x409;
        let label = feature.value_label(i, lang_id)?;
        if &label.as_bytes()[..name.len()] == name || feature.id() == tag {
            return Some(feature.value(i));
        }
    }
    None
}