1use crate::{Font, be_i16, be_u16, be_u32, find_table_full};
7use std::{collections::BTreeMap, ops::Range};
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Direction {
10 LeftToRight,
11 RightToLeft,
12}
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Feature {
15 pub tag: [u8; 4],
16 pub enabled: bool,
17}
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ShapeOptions<'a> {
20 pub script: [u8; 4],
21 pub language: [u8; 4],
23 pub direction: Direction,
24 pub features: &'a [Feature],
25}
26impl Default for ShapeOptions<'_> {
27 fn default() -> Self {
28 Self {
29 script: *b"latn",
30 language: *b"dflt",
31 direction: Direction::LeftToRight,
32 features: &[],
33 }
34 }
35}
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct ShapedGlyph {
38 pub glyph_id: u16,
39 pub cluster: Range<usize>,
43 pub x_advance: i32,
44 pub y_advance: i32,
45 pub x_offset: i32,
46 pub y_offset: i32,
47}
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct ShapedRun {
50 pub glyphs: Vec<ShapedGlyph>,
51 pub direction: Direction,
52 source: String,
53}
54impl ShapedRun {
55 pub fn logical_text(&self) -> &str {
57 &self.source
58 }
59}
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum ShapeErrorKind {
62 UnsupportedScript,
63 UnsupportedLanguage,
64 UnsupportedFeature,
65 UnsupportedLookup,
66 MissingGlyph,
67 UnpositionedMark,
68 MalformedFont,
69 BudgetExceeded,
70}
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct ShapeError {
73 pub kind: ShapeErrorKind,
74 pub table: Option<[u8; 4]>,
75 pub offset: Option<usize>,
76 pub text_offset: Option<usize>,
77}
78impl core::fmt::Display for ShapeError {
79 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
80 write!(
81 f,
82 "shaping {:?}, table {:?}, offset {:?}, text byte {:?}",
83 self.kind, self.table, self.offset, self.text_offset
84 )
85 }
86}
87impl std::error::Error for ShapeError {}
88fn error(kind: ShapeErrorKind) -> ShapeError {
89 ShapeError {
90 kind,
91 table: None,
92 offset: None,
93 text_offset: None,
94 }
95}
96#[derive(Clone, Copy)]
97struct Table<'a> {
98 d: &'a [u8],
99 tag: [u8; 4],
100 fuel: &'a std::cell::Cell<usize>,
101}
102impl Table<'_> {
103 fn bad(&self, p: usize) -> ShapeError {
104 ShapeError {
105 kind: ShapeErrorKind::MalformedFont,
106 table: Some(self.tag),
107 offset: Some(p),
108 text_offset: None,
109 }
110 }
111 fn u16(&self, p: usize) -> Result<u16, ShapeError> {
112 self.fuel.set(
113 self.fuel
114 .get()
115 .checked_sub(1)
116 .ok_or(error(ShapeErrorKind::BudgetExceeded))?,
117 );
118 be_u16(self.d, p).ok_or(self.bad(p))
119 }
120 fn i16(&self, p: usize) -> Result<i16, ShapeError> {
121 be_i16(self.d, p).ok_or(self.bad(p))
122 }
123 fn offset(&self, base: usize, p: usize) -> Result<usize, ShapeError> {
124 let n = self.u16(p)?;
125 if n == 0 {
126 return Err(self.bad(p));
127 }
128 let o = base + usize::from(n);
129 if o >= self.d.len() {
130 return Err(self.bad(p));
131 }
132 Ok(o)
133 }
134 fn tag(&self, p: usize) -> Result<[u8; 4], ShapeError> {
135 self.d
136 .get(p..p + 4)
137 .and_then(|v| v.try_into().ok())
138 .ok_or(self.bad(p))
139 }
140 fn coverage(&self, p: usize, g: u16) -> Result<Option<usize>, ShapeError> {
141 let count = usize::from(self.u16(p + 2)?);
142 match self.u16(p)? {
143 1 => {
144 let mut last = None;
145 let mut result = None;
146 for i in 0..count {
147 let id = self.u16(p + 4 + i * 2)?;
148 if last.is_some_and(|v| v >= id) {
149 return Err(self.bad(p));
150 }
151 if id == g {
152 result = Some(i);
153 }
154 last = Some(id);
155 }
156 Ok(result)
157 }
158 2 => {
159 let mut previous = None;
160 let mut expected = 0usize;
161 let mut result = None;
162 for i in 0..count {
163 let r = p + 4 + i * 6;
164 let a = self.u16(r)?;
165 let b = self.u16(r + 2)?;
166 let start = usize::from(self.u16(r + 4)?);
167 if a > b || previous.is_some_and(|v| v >= a) || start != expected {
168 return Err(self.bad(r));
169 }
170 if (a..=b).contains(&g) {
171 result = Some(start + usize::from(g - a));
172 }
173 expected += usize::from(b - a) + 1;
174 if expected > 65536 {
175 return Err(self.bad(r));
176 }
177 previous = Some(b);
178 }
179 Ok(result)
180 }
181 _ => Err(self.bad(p)),
182 }
183 }
184 fn class(&self, p: usize, gid: u16) -> Result<usize, ShapeError> {
185 match self.u16(p)? {
186 1 => {
187 let first = self.u16(p + 2)?;
188 let count = usize::from(self.u16(p + 4)?);
189 if gid < first || usize::from(gid - first) >= count {
190 return Ok(0);
191 }
192 Ok(usize::from(self.u16(p + 6 + usize::from(gid - first) * 2)?))
193 }
194 2 => {
195 let mut previous = None;
196 let mut result = 0;
197 for i in 0..usize::from(self.u16(p + 2)?) {
198 let r = p + 4 + i * 6;
199 let a = self.u16(r)?;
200 let b = self.u16(r + 2)?;
201 if a > b || previous.is_some_and(|v| v >= a) {
202 return Err(self.bad(r));
203 }
204 if (a..=b).contains(&gid) {
205 result = usize::from(self.u16(r + 4)?);
206 }
207 previous = Some(b);
208 }
209 Ok(result)
210 }
211 _ => Err(self.bad(p)),
212 }
213 }
214 fn anchor(&self, p: usize) -> Result<(i32, i32), ShapeError> {
215 if self.u16(p)? != 1 {
216 return Err(ShapeError {
217 kind: ShapeErrorKind::UnsupportedLookup,
218 ..self.bad(p)
219 });
220 }
221 Ok((i32::from(self.i16(p + 2)?), i32::from(self.i16(p + 4)?)))
222 }
223}
224#[derive(Clone)]
225struct Item {
226 g: ShapedGlyph,
227 mark: bool,
228 positioned: bool,
229 form: [u8; 4],
230}
231fn is_mark(c: char) -> bool {
232 matches!(c as u32,0x0300..=0x036f|0x064b..=0x065f|0x0670)
233}
234fn joining(c: char) -> u8 {
235 match c as u32 {
237 0x0620
238 | 0x0626
239 | 0x0628
240 | 0x062a..=0x062e
241 | 0x0633..=0x063f
242 | 0x0640..=0x0647
243 | 0x0649..=0x064a => 2,
244 0x0622..=0x0625 | 0x0627 | 0x0629 | 0x062f..=0x0632 | 0x0648 => 1,
245 _ => 0,
246 }
247}
248fn features(
249 t: Table<'_>,
250 opts: &ShapeOptions<'_>,
251) -> Result<BTreeMap<[u8; 4], Vec<u16>>, ShapeError> {
252 if t.u16(0)? != 1 || t.u16(2)? != 0 {
253 return Err(ShapeError {
254 kind: ShapeErrorKind::UnsupportedLookup,
255 ..t.bad(0)
256 });
257 }
258 let scripts = t.offset(0, 4)?;
259 let list = t.offset(0, 6)?;
260 let mut script = None;
261 for i in 0..usize::from(t.u16(scripts)?) {
262 let r = scripts + 2 + i * 6;
263 if t.tag(r)? == opts.script {
264 script = Some(t.offset(scripts, r + 4)?);
265 }
266 }
267 let Some(script) = script else {
268 return Ok(BTreeMap::new());
269 };
270 let mut lang = if opts.language == *b"dflt" && t.u16(script)? != 0 {
271 Some(t.offset(script, script)?)
272 } else {
273 None
274 };
275 for i in 0..usize::from(t.u16(script + 2)?) {
276 let r = script + 4 + i * 6;
277 if t.tag(r)? == opts.language {
278 lang = Some(t.offset(script, r + 4)?);
279 }
280 }
281 let lang = lang.ok_or(error(ShapeErrorKind::UnsupportedLanguage))?;
282 if t.u16(lang)? != 0 {
283 return Err(t.bad(lang));
284 }
285 let mut ids = Vec::new();
286 let required = t.u16(lang + 2)?;
287 if required != u16::MAX {
288 ids.push(required);
289 }
290 for i in 0..usize::from(t.u16(lang + 4)?) {
291 ids.push(t.u16(lang + 6 + i * 2)?);
292 }
293 let n = t.u16(list)?;
294 let mut out: BTreeMap<[u8; 4], Vec<u16>> = BTreeMap::new();
295 for id in ids {
296 if id >= n {
297 return Err(t.bad(list));
298 }
299 let r = list + 2 + usize::from(id) * 6;
300 let tag = t.tag(r)?;
301 if id == required
302 && (![
303 *b"ccmp", *b"locl", *b"rlig", *b"liga", *b"isol", *b"init", *b"medi", *b"fina",
304 *b"kern", *b"mark", *b"mkmk",
305 ]
306 .contains(&tag)
307 || !enabled(opts, tag))
308 {
309 return Err(ShapeError {
310 kind: ShapeErrorKind::UnsupportedFeature,
311 ..t.bad(r)
312 });
313 }
314 let f = t.offset(list, r + 4)?;
315 if t.u16(f)? != 0 {
316 return Err(ShapeError {
317 kind: ShapeErrorKind::UnsupportedFeature,
318 ..t.bad(f)
319 });
320 }
321 let dest = out.entry(tag).or_default();
322 for i in 0..usize::from(t.u16(f + 2)?) {
323 dest.push(t.u16(f + 4 + i * 2)?);
324 }
325 }
326 Ok(out)
327}
328fn table<'a>(
329 font: &'a Font,
330 tag: [u8; 4],
331 fuel: &'a std::cell::Cell<usize>,
332) -> Result<Option<Table<'a>>, ShapeError> {
333 let Some((o, n)) = find_table_full(&font.data, &tag) else {
334 return Ok(None);
335 };
336 let d = font
337 .data
338 .get(
339 o..o.checked_add(n)
340 .ok_or(error(ShapeErrorKind::MalformedFont))?,
341 )
342 .ok_or(error(ShapeErrorKind::MalformedFont))?;
343 Ok(Some(Table { d, tag, fuel }))
344}
345fn enabled(opts: &ShapeOptions<'_>, tag: [u8; 4]) -> bool {
346 opts.features
347 .iter()
348 .find(|f| f.tag == tag)
349 .is_none_or(|f| f.enabled)
350}
351fn spend(fuel: &mut usize) -> Result<(), ShapeError> {
352 *fuel = fuel
353 .checked_sub(1)
354 .ok_or(error(ShapeErrorKind::BudgetExceeded))?;
355 Ok(())
356}
357fn lookup(t: Table<'_>, id: u16) -> Result<(u16, u16, Vec<usize>), ShapeError> {
358 let list = t.offset(0, 8)?;
359 if id >= t.u16(list)? {
360 return Err(t.bad(list));
361 }
362 let p = t.offset(list, list + 2 + usize::from(id) * 2)?;
363 let kind = t.u16(p)?;
364 let flags = t.u16(p + 2)?;
365 if flags & !8 != 0 {
366 return Err(ShapeError {
367 kind: ShapeErrorKind::UnsupportedLookup,
368 ..t.bad(p + 2)
369 });
370 }
371 let mut subtables = Vec::new();
372 for i in 0..usize::from(t.u16(p + 4)?) {
373 subtables.push(t.offset(p, p + 6 + i * 2)?);
374 }
375 Ok((kind, flags, subtables))
376}
377fn extension(t: Table<'_>, kind: u16, p: usize) -> Result<(u16, usize), ShapeError> {
378 let wrapper = if t.tag == *b"GSUB" { 7 } else { 9 };
379 if kind != wrapper {
380 return Ok((kind, p));
381 }
382 if t.u16(p)? != 1 {
383 return Err(t.bad(p));
384 }
385 let actual = t.u16(p + 2)?;
386 if actual == wrapper {
387 return Err(t.bad(p));
388 }
389 let offset = be_u32(t.d, p + 4).ok_or(t.bad(p + 4))? as usize;
390 let next = p
391 .checked_add(offset)
392 .filter(|&x| x < t.d.len())
393 .ok_or(t.bad(p + 4))?;
394 Ok((actual, next))
395}
396fn substitute(
397 t: Table<'_>,
398 ids: &[u16],
399 items: &mut Vec<Item>,
400 form: Option<[u8; 4]>,
401 fuel: &mut usize,
402) -> Result<(), ShapeError> {
403 for &id in ids {
404 let (kind, flags, subs) = lookup(t, id)?;
405 for item_index in 0..items.len() {
406 if item_index >= items.len() {
408 break;
409 }
410 spend(fuel)?;
411 if form.is_some_and(|f| items[item_index].form != f)
412 || flags & 8 != 0 && items[item_index].mark
413 {
414 continue;
415 }
416 for &sub in &subs {
417 let (kind, p) = extension(t, kind, sub)?;
418 if !matches!(kind, 1 | 4) {
419 return Err(ShapeError {
420 kind: ShapeErrorKind::UnsupportedLookup,
421 ..t.bad(p)
422 });
423 }
424 let coverage = t.offset(p, p + 2)?;
425 let Some(ci) = t.coverage(coverage, items[item_index].g.glyph_id)? else {
426 continue;
427 };
428 if kind == 1 {
429 let gid = match t.u16(p)? {
430 1 => items[item_index].g.glyph_id.wrapping_add(t.u16(p + 4)?),
431 2 => {
432 if ci >= usize::from(t.u16(p + 4)?) {
433 return Err(t.bad(p));
434 }
435 t.u16(p + 6 + ci * 2)?
436 }
437 _ => return Err(t.bad(p)),
438 };
439 items[item_index].g.glyph_id = gid;
440 break;
441 }
442 if t.u16(p)? != 1 || ci >= usize::from(t.u16(p + 4)?) {
443 return Err(t.bad(p));
444 }
445 let set = t.offset(p, p + 6 + ci * 2)?;
446 let mut matched = false;
447 for n in 0..usize::from(t.u16(set)?) {
448 spend(fuel)?;
449 let lig = t.offset(set, set + 2 + n * 2)?;
450 let count = usize::from(t.u16(lig + 2)?);
451 if count < 2 {
452 return Err(t.bad(lig));
453 }
454 let mut indices = vec![item_index];
455 let mut next = item_index + 1;
456 for j in 1..count {
457 while next < items.len() && flags & 8 != 0 && items[next].mark {
458 next += 1;
459 }
460 if next >= items.len()
461 || items[next].g.glyph_id != t.u16(lig + 2 + j * 2)?
462 {
463 break;
464 }
465 indices.push(next);
466 next += 1;
467 }
468 if indices.len() == count {
469 let end = items[*indices.last().ok_or(t.bad(lig))?].g.cluster.end;
470 items[item_index].g.cluster.end = end;
471 items[item_index].g.glyph_id = t.u16(lig)?;
472 let cluster = items[item_index].g.cluster.clone();
474 for item in &mut items[item_index..next] {
475 item.g.cluster = cluster.clone();
476 }
477 for &i in indices[1..].iter().rev() {
478 items.remove(i);
479 }
480 matched = true;
481 break;
482 }
483 }
484 if matched {
485 break;
486 }
487 }
488 }
489 }
490 Ok(())
491}
492fn value(t: Table<'_>, p: usize, format: u16) -> Result<([i32; 4], usize), ShapeError> {
493 if format & !15 != 0 {
494 return Err(ShapeError {
495 kind: ShapeErrorKind::UnsupportedLookup,
496 ..t.bad(p)
497 });
498 }
499 let mut values = [0; 4];
500 let mut q = p;
501 for (i, v) in values.iter_mut().enumerate() {
502 if format & (1 << i) != 0 {
503 *v = i32::from(t.i16(q)?);
504 q += 2;
505 }
506 }
507 Ok((values, q))
508}
509fn apply(g: &mut ShapedGlyph, v: [i32; 4]) -> Result<(), ShapeError> {
510 for (dst, delta) in [
511 (&mut g.x_offset, v[0]),
512 (&mut g.y_offset, v[1]),
513 (&mut g.x_advance, v[2]),
514 (&mut g.y_advance, v[3]),
515 ] {
516 *dst = dst
517 .checked_add(delta)
518 .ok_or(error(ShapeErrorKind::BudgetExceeded))?;
519 }
520 Ok(())
521}
522
523fn position(
524 t: Table<'_>,
525 ids: &[u16],
526 items: &mut [Item],
527 direction: Direction,
528 fuel: &mut usize,
529) -> Result<(), ShapeError> {
530 for &id in ids {
531 let (kind, flags, subs) = lookup(t, id)?;
532 for step in 0..items.len() {
533 let i = if direction == Direction::RightToLeft {
534 items.len() - 1 - step
535 } else {
536 step
537 };
538 spend(fuel)?;
539 if flags & 8 != 0 && items[i].mark {
540 continue;
541 }
542 for &sub in &subs {
543 let (kind, p) = extension(t, kind, sub)?;
544 if !matches!(kind, 1 | 2 | 4 | 6) {
545 return Err(ShapeError {
546 kind: ShapeErrorKind::UnsupportedLookup,
547 ..t.bad(p)
548 });
549 }
550 let Some(ci) = t.coverage(t.offset(p, p + 2)?, items[i].g.glyph_id)? else {
551 continue;
552 };
553 if kind == 1 {
554 let format = t.u16(p + 4)?;
555 let q = match t.u16(p)? {
556 1 => p + 6,
557 2 => {
558 if ci >= usize::from(t.u16(p + 6)?) {
559 return Err(t.bad(p));
560 }
561 p + 8 + ci * format.count_ones() as usize * 2
562 }
563 _ => return Err(t.bad(p)),
564 };
565 let (v, _) = value(t, q, format)?;
566 apply(&mut items[i].g, v)?;
567 break;
568 }
569 if kind == 2 {
570 let pair_format = t.u16(p)?;
571 if !matches!(pair_format, 1 | 2) {
572 return Err(t.bad(p));
573 }
574 let candidate = match direction {
575 Direction::LeftToRight => {
576 (i + 1..items.len()).find(|&j| flags & 8 == 0 || !items[j].mark)
577 }
578 Direction::RightToLeft => {
579 (0..i).rev().find(|&j| flags & 8 == 0 || !items[j].mark)
580 }
581 };
582 let Some(j) = candidate else {
583 continue;
584 };
585 let f1 = t.u16(p + 4)?;
586 let f2 = t.u16(p + 6)?;
587 if pair_format == 2 {
588 let a = t.class(t.offset(p, p + 8)?, items[i].g.glyph_id)?;
589 let b = t.class(t.offset(p, p + 10)?, items[j].g.glyph_id)?;
590 let n1 = usize::from(t.u16(p + 12)?);
591 let n2 = usize::from(t.u16(p + 14)?);
592 if n1.checked_mul(n2).is_none_or(|n| n > 1_000_000) {
593 return Err(error(ShapeErrorKind::BudgetExceeded));
594 }
595 if a >= n1 || b >= n2 {
596 return Err(t.bad(p));
597 }
598 let size = (f1.count_ones() + f2.count_ones()) as usize * 2;
599 let q = p + 16 + (a * n2 + b) * size;
600 let (v, next) = value(t, q, f1)?;
601 let (w, _) = value(t, next, f2)?;
602 apply(&mut items[i].g, v)?;
603 apply(&mut items[j].g, w)?;
604 break;
605 }
606 if ci >= usize::from(t.u16(p + 8)?) {
607 return Err(t.bad(p));
608 }
609 let set = t.offset(p, p + 10 + ci * 2)?;
610 let mut q = set + 2;
611 let mut found = false;
612 for _ in 0..t.u16(set)? {
613 spend(fuel)?;
614 let gid = t.u16(q)?;
615 let (a, next) = value(t, q + 2, f1)?;
616 let (b, next) = value(t, next, f2)?;
617 q = next;
618 if gid == items[j].g.glyph_id {
619 apply(&mut items[i].g, a)?;
620 apply(&mut items[j].g, b)?;
621 found = true;
622 break;
623 }
624 }
625 if found {
626 break;
627 }
628 continue;
629 }
630 if t.u16(p)? != 1 {
631 return Err(t.bad(p));
632 }
633 if !items[i].mark {
634 return Err(t.bad(p));
635 }
636 let candidate = match direction {
637 Direction::LeftToRight => (0..i).rev().find(|&j| items[j].mark == (kind == 6)),
638 Direction::RightToLeft => {
639 (i + 1..items.len()).find(|&j| items[j].mark == (kind == 6))
640 }
641 };
642 let Some(j) = candidate else {
643 continue;
644 };
645 if kind == 6 && items[j.min(i) + 1..j.max(i)].iter().any(|v| !v.mark) {
646 continue;
647 }
648 let Some(bi) = t.coverage(t.offset(p, p + 4)?, items[j].g.glyph_id)? else {
649 continue;
650 };
651 let classes = usize::from(t.u16(p + 6)?);
652 if classes > 256 {
653 return Err(error(ShapeErrorKind::BudgetExceeded));
654 }
655 let marks = t.offset(p, p + 8)?;
656 let bases = t.offset(p, p + 10)?;
657 if ci >= usize::from(t.u16(marks)?) || bi >= usize::from(t.u16(bases)?) {
658 return Err(t.bad(p));
659 }
660 let class = usize::from(t.u16(marks + 2 + ci * 4)?);
661 if class >= classes {
662 return Err(t.bad(marks));
663 }
664 let ma = t.anchor(t.offset(marks, marks + 4 + ci * 4)?)?;
665 let anchor_slot = bases + 2 + (bi * classes + class) * 2;
666 if t.u16(anchor_slot)? == 0 {
667 continue;
668 }
669 let ba = t.anchor(t.offset(bases, anchor_slot)?)?;
670 let delta = if j < i {
671 -items[j..i]
672 .iter()
673 .map(|v| i64::from(v.g.x_advance))
674 .sum::<i64>()
675 } else {
676 items[i..j]
677 .iter()
678 .map(|v| i64::from(v.g.x_advance))
679 .sum::<i64>()
680 };
681 items[i].g.x_offset = i32::try_from(
682 delta + i64::from(items[j].g.x_offset) + i64::from(ba.0) - i64::from(ma.0),
683 )
684 .map_err(|_| error(ShapeErrorKind::BudgetExceeded))?;
685 items[i].g.y_offset = i32::try_from(
686 i64::from(items[j].g.y_offset) + i64::from(ba.1) - i64::from(ma.1),
687 )
688 .map_err(|_| error(ShapeErrorKind::BudgetExceeded))?;
689 items[i].positioned = true;
690 break;
691 }
692 }
693 }
694 Ok(())
695}
696impl Font {
697 pub fn shape(&self, text: &str, opts: &ShapeOptions<'_>) -> Result<ShapedRun, ShapeError> {
699 self.validate_font_metrics().map_err(|e| ShapeError {
700 kind: ShapeErrorKind::MalformedFont,
701 table: Some(e.table),
702 offset: e.offset,
703 text_offset: None,
704 })?;
705 let arabic = opts.script == *b"arab";
706 if opts.script != *b"latn" && !arabic {
707 return Err(error(ShapeErrorKind::UnsupportedScript));
708 }
709 if (arabic && opts.direction != Direction::RightToLeft)
710 || (!arabic && opts.direction != Direction::LeftToRight)
711 {
712 return Err(error(ShapeErrorKind::UnsupportedScript));
713 }
714 if text.len() > 65536 {
715 return Err(error(ShapeErrorKind::BudgetExceeded));
716 }
717 let allowed = [
718 *b"ccmp", *b"locl", *b"rlig", *b"liga", *b"kern", *b"mark", *b"mkmk", *b"isol",
719 *b"init", *b"medi", *b"fina",
720 ];
721 for (i, f) in opts.features.iter().enumerate() {
722 if !allowed.contains(&f.tag) || opts.features[..i].iter().any(|v| v.tag == f.tag) {
723 return Err(error(ShapeErrorKind::UnsupportedFeature));
724 }
725 }
726 let chars: Vec<_> = text.char_indices().collect();
727 if chars.len() > 4096 {
728 return Err(error(ShapeErrorKind::BudgetExceeded));
729 }
730 let mut items: Vec<Item> = Vec::new();
731 for (i, &(offset, ch)) in chars.iter().enumerate() {
732 let cp = ch as u32;
733 let mark = is_mark(ch);
734 let covered = if arabic {
735 matches!(cp,0x20..=0x40|0x5b..=0x60|0x7b..=0x7e|0x0620..=0x064a|0x0660..=0x0669)
736 || mark
737 } else {
738 matches!(cp,0x20..=0x024f|0x0300..=0x036f)
739 };
740 if !covered {
741 return Err(ShapeError {
742 text_offset: Some(offset),
743 ..error(ShapeErrorKind::UnsupportedScript)
744 });
745 }
746 let gid = self.glyph_index(ch);
747 if gid == 0 {
748 return Err(ShapeError {
749 text_offset: Some(offset),
750 ..error(ShapeErrorKind::MissingGlyph)
751 });
752 }
753 if gid >= self.num_glyphs {
754 return Err(error(ShapeErrorKind::MalformedFont));
755 }
756 let end = offset + ch.len_utf8();
757 let start = if mark {
758 items.last().map_or(offset, |v| v.g.cluster.start)
759 } else {
760 offset
761 };
762 if mark && i > 64 && chars[i - 64..i].iter().all(|(_, c)| is_mark(*c)) {
763 return Err(error(ShapeErrorKind::BudgetExceeded));
764 }
765 if mark {
766 for prev in items
767 .iter_mut()
768 .rev()
769 .take_while(|v| v.g.cluster.start == start)
770 {
771 prev.g.cluster.end = end;
772 }
773 }
774 let mut form = *b"isol";
775 if arabic && !mark {
776 let current = joining(ch);
777 let prev = chars[..i]
778 .iter()
779 .rev()
780 .find(|(_, c)| !is_mark(*c))
781 .map_or(0, |(_, c)| joining(*c));
782 let next = chars[i + 1..]
783 .iter()
784 .find(|(_, c)| !is_mark(*c))
785 .map_or(0, |(_, c)| joining(*c));
786 let before = prev == 2 && current != 0;
787 let after = current == 2 && next != 0;
788 form = match (before, after) {
789 (true, true) => *b"medi",
790 (true, false) => *b"fina",
791 (false, true) => *b"init",
792 _ => *b"isol",
793 };
794 }
795 items.push(Item {
796 g: ShapedGlyph {
797 glyph_id: gid,
798 cluster: start..end,
799 x_advance: 0,
800 y_advance: 0,
801 x_offset: 0,
802 y_offset: 0,
803 },
804 mark,
805 positioned: false,
806 form,
807 });
808 }
809 let mut fuel = 1_000_000;
810 let read_budget = std::cell::Cell::new(2_000_000);
811 let gsub = table(self, *b"GSUB", &read_budget)?;
812 let gpos = table(self, *b"GPOS", &read_budget)?;
813 let gs = if let Some(t) = gsub {
814 features(t, opts)?
815 } else {
816 BTreeMap::new()
817 };
818 let gp = if let Some(t) = gpos {
819 features(t, opts)?
820 } else {
821 BTreeMap::new()
822 };
823 if opts.language != *b"dflt" && gs.is_empty() && gp.is_empty() {
824 return Err(error(ShapeErrorKind::UnsupportedLanguage));
825 }
826 if arabic && gp.contains_key(b"curs") {
827 return Err(error(ShapeErrorKind::UnsupportedLookup));
828 }
829 if arabic {
830 for item in &items {
831 if item.form != *b"isol"
832 && !item.mark
833 && enabled(opts, item.form)
834 && !gs.contains_key(&item.form)
835 {
836 return Err(error(ShapeErrorKind::UnsupportedFeature));
837 }
838 }
839 }
840 for f in opts.features {
841 if f.enabled && !gs.contains_key(&f.tag) && !gp.contains_key(&f.tag) {
842 return Err(error(ShapeErrorKind::UnsupportedFeature));
843 }
844 }
845 if let Some(t) = gsub {
846 for tag in [
847 *b"ccmp", *b"locl", *b"isol", *b"fina", *b"medi", *b"init", *b"rlig", *b"liga",
848 ] {
849 if enabled(opts, tag)
850 && let Some(ids) = gs.get(&tag)
851 {
852 let form = if [*b"isol", *b"fina", *b"medi", *b"init"].contains(&tag) {
853 if !arabic {
854 continue;
855 }
856 Some(tag)
857 } else {
858 None
859 };
860 substitute(t, ids, &mut items, form, &mut fuel)?;
861 }
862 }
863 }
864 for item in &mut items {
865 if item.g.glyph_id >= self.num_glyphs {
866 return Err(error(ShapeErrorKind::MalformedFont));
867 }
868 item.g.x_advance = if item.mark {
869 0
870 } else {
871 i32::from(self.advance_width(item.g.glyph_id))
872 };
873 }
874 if opts.direction == Direction::RightToLeft {
875 items.reverse();
876 }
877 if let Some(t) = gpos {
878 for tag in [*b"kern", *b"mark", *b"mkmk"] {
879 if enabled(opts, tag)
880 && let Some(ids) = gp.get(&tag)
881 {
882 position(t, ids, &mut items, opts.direction, &mut fuel)?;
883 }
884 }
885 }
886 for item in &items {
887 if item.mark && !item.positioned {
888 return Err(ShapeError {
889 text_offset: Some(item.g.cluster.start),
890 ..error(ShapeErrorKind::UnpositionedMark)
891 });
892 }
893 }
894 Ok(ShapedRun {
895 glyphs: items.into_iter().map(|i| i.g).collect(),
896 direction: opts.direction,
897 source: text.to_owned(),
898 })
899 }
900}