1 | use ttf_parser::gpos::*; |
2 | use ttf_parser::opentype_layout::LookupIndex; |
3 | use ttf_parser::GlyphId; |
4 | |
5 | use crate::buffer::{Buffer, BufferScratchFlags, GlyphPosition}; |
6 | use crate::plan::ShapePlan; |
7 | use crate::{Direction, Face}; |
8 | |
9 | use super::apply::{Apply, ApplyContext}; |
10 | use super::matching::SkippyIter; |
11 | use super::{ |
12 | lookup_flags, LayoutLookup, LayoutTable, PositioningLookup, PositioningTable, TableIndex, |
13 | }; |
14 | |
15 | pub fn position_start(_: &Face, buffer: &mut Buffer) { |
16 | let len: usize = buffer.len; |
17 | for pos: &mut GlyphPosition in &mut buffer.pos[..len] { |
18 | pos.set_attach_chain(0); |
19 | pos.set_attach_type(0); |
20 | } |
21 | } |
22 | |
23 | pub fn position(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) { |
24 | super::apply_layout_table(plan, face, buffer, table:face.gpos.as_ref()); |
25 | } |
26 | |
27 | pub fn position_finish_advances(_: &Face, _: &mut Buffer) {} |
28 | |
29 | pub fn position_finish_offsets(_: &Face, buffer: &mut Buffer) { |
30 | let len: usize = buffer.len; |
31 | let direction: Direction = buffer.direction; |
32 | |
33 | // Handle attachments |
34 | if bufferBufferScratchFlags |
35 | .scratch_flags |
36 | .contains(BufferScratchFlags::HAS_GPOS_ATTACHMENT) |
37 | { |
38 | for i: usize in 0..len { |
39 | propagate_attachment_offsets(&mut buffer.pos, len, i, direction); |
40 | } |
41 | } |
42 | } |
43 | |
44 | fn propagate_attachment_offsets( |
45 | pos: &mut [GlyphPosition], |
46 | len: usize, |
47 | i: usize, |
48 | direction: Direction, |
49 | ) { |
50 | // Adjusts offsets of attached glyphs (both cursive and mark) to accumulate |
51 | // offset of glyph they are attached to. |
52 | let chain = pos[i].attach_chain(); |
53 | let kind = pos[i].attach_type(); |
54 | if chain == 0 { |
55 | return; |
56 | } |
57 | |
58 | pos[i].set_attach_chain(0); |
59 | |
60 | let j = (i as isize + isize::from(chain)) as _; |
61 | if j >= len { |
62 | return; |
63 | } |
64 | |
65 | propagate_attachment_offsets(pos, len, j, direction); |
66 | |
67 | match kind { |
68 | attach_type::MARK => { |
69 | pos[i].x_offset += pos[j].x_offset; |
70 | pos[i].y_offset += pos[j].y_offset; |
71 | |
72 | assert!(j < i); |
73 | if direction.is_forward() { |
74 | for k in j..i { |
75 | pos[i].x_offset -= pos[k].x_advance; |
76 | pos[i].y_offset -= pos[k].y_advance; |
77 | } |
78 | } else { |
79 | for k in j + 1..i + 1 { |
80 | pos[i].x_offset += pos[k].x_advance; |
81 | pos[i].y_offset += pos[k].y_advance; |
82 | } |
83 | } |
84 | } |
85 | attach_type::CURSIVE => { |
86 | if direction.is_horizontal() { |
87 | pos[i].y_offset += pos[j].y_offset; |
88 | } else { |
89 | pos[i].x_offset += pos[j].x_offset; |
90 | } |
91 | } |
92 | _ => {} |
93 | } |
94 | } |
95 | |
96 | impl<'a> LayoutTable for PositioningTable<'a> { |
97 | const INDEX: TableIndex = TableIndex::GPOS; |
98 | const IN_PLACE: bool = true; |
99 | |
100 | type Lookup = PositioningLookup<'a>; |
101 | |
102 | fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup> { |
103 | self.lookups.get(index:usize::from(index)) |
104 | } |
105 | } |
106 | |
107 | impl LayoutLookup for PositioningLookup<'_> { |
108 | fn props(&self) -> u32 { |
109 | self.props |
110 | } |
111 | |
112 | fn is_reverse(&self) -> bool { |
113 | false |
114 | } |
115 | |
116 | fn covers(&self, glyph: GlyphId) -> bool { |
117 | self.coverage.contains(glyph) |
118 | } |
119 | } |
120 | |
121 | impl Apply for PositioningLookup<'_> { |
122 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
123 | if self.covers(ctx.buffer.cur(0).as_glyph()) { |
124 | for subtable: &PositioningSubtable<'_> in &self.subtables { |
125 | if subtable.apply(ctx).is_some() { |
126 | return Some(()); |
127 | } |
128 | } |
129 | } |
130 | |
131 | None |
132 | } |
133 | } |
134 | |
135 | impl Apply for PositioningSubtable<'_> { |
136 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
137 | match self { |
138 | Self::Single(t: &SingleAdjustment<'_>) => t.apply(ctx), |
139 | Self::Pair(t: &PairAdjustment<'_>) => t.apply(ctx), |
140 | Self::Cursive(t: &CursiveAdjustment<'_>) => t.apply(ctx), |
141 | Self::MarkToBase(t: &MarkToBaseAdjustment<'_>) => t.apply(ctx), |
142 | Self::MarkToLigature(t: &MarkToLigatureAdjustment<'_>) => t.apply(ctx), |
143 | Self::MarkToMark(t: &MarkToMarkAdjustment<'_>) => t.apply(ctx), |
144 | Self::Context(t: &ContextLookup<'_>) => t.apply(ctx), |
145 | Self::ChainContext(t: &ChainedContextLookup<'_>) => t.apply(ctx), |
146 | } |
147 | } |
148 | } |
149 | |
150 | impl Apply for SingleAdjustment<'_> { |
151 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
152 | let glyph: GlyphId = ctx.buffer.cur(0).as_glyph(); |
153 | let record: ValueRecord<'_> = match self { |
154 | Self::Format1 { coverage: &Coverage<'_>, value: &ValueRecord<'_> } => { |
155 | coverage.get(glyph)?; |
156 | *value |
157 | } |
158 | Self::Format2 { coverage: &Coverage<'_>, values: &ValueRecordsArray<'_> } => { |
159 | let index: u16 = coverage.get(glyph)?; |
160 | values.get(index)? |
161 | } |
162 | }; |
163 | record.apply(ctx, ctx.buffer.idx); |
164 | ctx.buffer.idx += 1; |
165 | Some(()) |
166 | } |
167 | } |
168 | |
169 | impl Apply for PairAdjustment<'_> { |
170 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
171 | let first = ctx.buffer.cur(0).as_glyph(); |
172 | let index = self.coverage().get(first)?; |
173 | |
174 | let mut iter = SkippyIter::new(ctx, ctx.buffer.idx, 1, false); |
175 | if !iter.next() { |
176 | return None; |
177 | } |
178 | |
179 | let pos = iter.index(); |
180 | let second = ctx.buffer.info[pos].as_glyph(); |
181 | |
182 | let records = match self { |
183 | Self::Format1 { sets, .. } => sets.get(index)?.get(second), |
184 | Self::Format2 { |
185 | classes, matrix, .. |
186 | } => { |
187 | let classes = (classes.0.get(first), classes.1.get(second)); |
188 | matrix.get(classes) |
189 | } |
190 | }?; |
191 | |
192 | let flag1 = records.0.apply(ctx, ctx.buffer.idx); |
193 | let flag2 = records.1.apply(ctx, pos); |
194 | // Note the intentional use of "|" instead of short-circuit "||". |
195 | if flag1 | flag2 { |
196 | ctx.buffer.unsafe_to_break(ctx.buffer.idx, pos + 1); |
197 | } |
198 | |
199 | ctx.buffer.idx = pos + usize::from(flag2); |
200 | Some(()) |
201 | } |
202 | } |
203 | |
204 | impl Apply for CursiveAdjustment<'_> { |
205 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
206 | let this = ctx.buffer.cur(0).as_glyph(); |
207 | |
208 | let index_this = self.coverage.get(this)?; |
209 | let entry_this = self.sets.entry(index_this)?; |
210 | |
211 | let mut iter = SkippyIter::new(ctx, ctx.buffer.idx, 1, false); |
212 | if !iter.prev() { |
213 | return None; |
214 | } |
215 | |
216 | let i = iter.index(); |
217 | let prev = ctx.buffer.info[i].as_glyph(); |
218 | let index_prev = self.coverage.get(prev)?; |
219 | let exit_prev = self.sets.exit(index_prev)?; |
220 | |
221 | let (exit_x, exit_y) = exit_prev.get(ctx.face); |
222 | let (entry_x, entry_y) = entry_this.get(ctx.face); |
223 | |
224 | let direction = ctx.buffer.direction; |
225 | let j = ctx.buffer.idx; |
226 | ctx.buffer.unsafe_to_break(i, j); |
227 | |
228 | let pos = &mut ctx.buffer.pos; |
229 | match direction { |
230 | Direction::LeftToRight => { |
231 | pos[i].x_advance = exit_x + pos[i].x_offset; |
232 | let d = entry_x + pos[j].x_offset; |
233 | pos[j].x_advance -= d; |
234 | pos[j].x_offset -= d; |
235 | } |
236 | Direction::RightToLeft => { |
237 | let d = exit_x + pos[i].x_offset; |
238 | pos[i].x_advance -= d; |
239 | pos[i].x_offset -= d; |
240 | pos[j].x_advance = entry_x + pos[j].x_offset; |
241 | } |
242 | Direction::TopToBottom => { |
243 | pos[i].y_advance = exit_y + pos[i].y_offset; |
244 | let d = entry_y + pos[j].y_offset; |
245 | pos[j].y_advance -= d; |
246 | pos[j].y_offset -= d; |
247 | } |
248 | Direction::BottomToTop => { |
249 | let d = exit_y + pos[i].y_offset; |
250 | pos[i].y_advance -= d; |
251 | pos[i].y_offset -= d; |
252 | pos[j].y_advance = entry_y; |
253 | } |
254 | Direction::Invalid => {} |
255 | } |
256 | |
257 | // Cross-direction adjustment |
258 | |
259 | // We attach child to parent (think graph theory and rooted trees whereas |
260 | // the root stays on baseline and each node aligns itself against its |
261 | // parent. |
262 | // |
263 | // Optimize things for the case of RightToLeft, as that's most common in |
264 | // Arabic. |
265 | let mut child = i; |
266 | let mut parent = j; |
267 | let mut x_offset = entry_x - exit_x; |
268 | let mut y_offset = entry_y - exit_y; |
269 | |
270 | // Low bits are lookup flags, so we want to truncate. |
271 | if ctx.lookup_props as u16 & lookup_flags::RIGHT_TO_LEFT == 0 { |
272 | core::mem::swap(&mut child, &mut parent); |
273 | x_offset = -x_offset; |
274 | y_offset = -y_offset; |
275 | } |
276 | |
277 | // If child was already connected to someone else, walk through its old |
278 | // chain and reverse the link direction, such that the whole tree of its |
279 | // previous connection now attaches to new parent. Watch out for case |
280 | // where new parent is on the path from old chain... |
281 | reverse_cursive_minor_offset(pos, child, direction, parent); |
282 | |
283 | pos[child].set_attach_type(attach_type::CURSIVE); |
284 | pos[child].set_attach_chain((parent as isize - child as isize) as i16); |
285 | |
286 | ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
287 | if direction.is_horizontal() { |
288 | pos[child].y_offset = y_offset; |
289 | } else { |
290 | pos[child].x_offset = x_offset; |
291 | } |
292 | |
293 | // If parent was attached to child, separate them. |
294 | // https://github.com/harfbuzz/harfbuzz/issues/2469 |
295 | if pos[parent].attach_chain() == -pos[child].attach_chain() { |
296 | pos[parent].set_attach_chain(0); |
297 | } |
298 | |
299 | ctx.buffer.idx += 1; |
300 | Some(()) |
301 | } |
302 | } |
303 | |
304 | fn reverse_cursive_minor_offset( |
305 | pos: &mut [GlyphPosition], |
306 | i: usize, |
307 | direction: Direction, |
308 | new_parent: usize, |
309 | ) { |
310 | let chain = pos[i].attach_chain(); |
311 | let attach_type = pos[i].attach_type(); |
312 | if chain == 0 || attach_type & attach_type::CURSIVE == 0 { |
313 | return; |
314 | } |
315 | |
316 | pos[i].set_attach_chain(0); |
317 | |
318 | // Stop if we see new parent in the chain. |
319 | let j = (i as isize + isize::from(chain)) as _; |
320 | if j == new_parent { |
321 | return; |
322 | } |
323 | |
324 | reverse_cursive_minor_offset(pos, j, direction, new_parent); |
325 | |
326 | if direction.is_horizontal() { |
327 | pos[j].y_offset = -pos[i].y_offset; |
328 | } else { |
329 | pos[j].x_offset = -pos[i].x_offset; |
330 | } |
331 | |
332 | pos[j].set_attach_chain(-chain); |
333 | pos[j].set_attach_type(attach_type); |
334 | } |
335 | |
336 | impl Apply for MarkToBaseAdjustment<'_> { |
337 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
338 | let buffer = &ctx.buffer; |
339 | let mark_glyph = ctx.buffer.cur(0).as_glyph(); |
340 | let mark_index = self.mark_coverage.get(mark_glyph)?; |
341 | |
342 | // Now we search backwards for a non-mark glyph |
343 | let mut iter = SkippyIter::new(ctx, buffer.idx, 1, false); |
344 | iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS)); |
345 | |
346 | let info = &buffer.info; |
347 | loop { |
348 | if !iter.prev() { |
349 | return None; |
350 | } |
351 | |
352 | // We only want to attach to the first of a MultipleSubst sequence. |
353 | // https://github.com/harfbuzz/harfbuzz/issues/740 |
354 | // Reject others... |
355 | // ...but stop if we find a mark in the MultipleSubst sequence: |
356 | // https://github.com/harfbuzz/harfbuzz/issues/1020 |
357 | let idx = iter.index(); |
358 | if !info[idx].is_multiplied() |
359 | || info[idx].lig_comp() == 0 |
360 | || idx == 0 |
361 | || info[idx - 1].is_mark() |
362 | || info[idx].lig_id() != info[idx - 1].lig_id() |
363 | || info[idx].lig_comp() != info[idx - 1].lig_comp() + 1 |
364 | { |
365 | break; |
366 | } |
367 | iter.reject(); |
368 | } |
369 | |
370 | // Checking that matched glyph is actually a base glyph by GDEF is too strong; disabled |
371 | |
372 | let idx = iter.index(); |
373 | let base_glyph = info[idx].as_glyph(); |
374 | let base_index = self.base_coverage.get(base_glyph)?; |
375 | |
376 | self.marks |
377 | .apply(ctx, self.anchors, mark_index, base_index, idx) |
378 | } |
379 | } |
380 | |
381 | impl Apply for MarkToLigatureAdjustment<'_> { |
382 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
383 | let buffer = &ctx.buffer; |
384 | let mark_glyph = ctx.buffer.cur(0).as_glyph(); |
385 | let mark_index = self.mark_coverage.get(mark_glyph)?; |
386 | |
387 | // Now we search backwards for a non-mark glyph |
388 | let mut iter = SkippyIter::new(ctx, buffer.idx, 1, false); |
389 | iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS)); |
390 | if !iter.prev() { |
391 | return None; |
392 | } |
393 | |
394 | // Checking that matched glyph is actually a ligature by GDEF is too strong; disabled |
395 | |
396 | let idx = iter.index(); |
397 | let lig_glyph = buffer.info[idx].as_glyph(); |
398 | let lig_index = self.ligature_coverage.get(lig_glyph)?; |
399 | let lig_attach = self.ligature_array.get(lig_index)?; |
400 | |
401 | // Find component to attach to |
402 | let comp_count = lig_attach.rows; |
403 | if comp_count == 0 { |
404 | return None; |
405 | } |
406 | |
407 | // We must now check whether the ligature ID of the current mark glyph |
408 | // is identical to the ligature ID of the found ligature. If yes, we |
409 | // can directly use the component index. If not, we attach the mark |
410 | // glyph to the last component of the ligature. |
411 | let lig_id = buffer.info[idx].lig_id(); |
412 | let mark_id = buffer.cur(0).lig_id(); |
413 | let mark_comp = u16::from(buffer.cur(0).lig_comp()); |
414 | let matches = lig_id != 0 && lig_id == mark_id && mark_comp > 0; |
415 | let comp_index = if matches { |
416 | mark_comp.min(comp_count) |
417 | } else { |
418 | comp_count |
419 | } - 1; |
420 | |
421 | self.marks |
422 | .apply(ctx, lig_attach, mark_index, comp_index, idx) |
423 | } |
424 | } |
425 | |
426 | impl Apply for MarkToMarkAdjustment<'_> { |
427 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
428 | let buffer = &ctx.buffer; |
429 | let mark1_glyph = ctx.buffer.cur(0).as_glyph(); |
430 | let mark1_index = self.mark1_coverage.get(mark1_glyph)?; |
431 | |
432 | // Now we search backwards for a suitable mark glyph until a non-mark glyph |
433 | let mut iter = SkippyIter::new(ctx, buffer.idx, 1, false); |
434 | iter.set_lookup_props(ctx.lookup_props & !u32::from(lookup_flags::IGNORE_FLAGS)); |
435 | if !iter.prev() { |
436 | return None; |
437 | } |
438 | |
439 | let idx = iter.index(); |
440 | if !buffer.info[idx].is_mark() { |
441 | return None; |
442 | } |
443 | |
444 | let id1 = buffer.cur(0).lig_id(); |
445 | let id2 = buffer.info[idx].lig_id(); |
446 | let comp1 = buffer.cur(0).lig_comp(); |
447 | let comp2 = buffer.info[idx].lig_comp(); |
448 | |
449 | let matches = if id1 == id2 { |
450 | // Marks belonging to the same base |
451 | // or marks belonging to the same ligature component. |
452 | id1 == 0 || comp1 == comp2 |
453 | } else { |
454 | // If ligature ids don't match, it may be the case that one of the marks |
455 | // itself is a ligature. In which case match. |
456 | (id1 > 0 && comp1 == 0) || (id2 > 0 && comp2 == 0) |
457 | }; |
458 | |
459 | if !matches { |
460 | return None; |
461 | } |
462 | |
463 | let mark2_glyph = buffer.info[idx].as_glyph(); |
464 | let mark2_index = self.mark2_coverage.get(mark2_glyph)?; |
465 | |
466 | self.marks |
467 | .apply(ctx, self.mark2_matrix, mark1_index, mark2_index, idx) |
468 | } |
469 | } |
470 | |
471 | trait ValueRecordExt { |
472 | fn apply(&self, ctx: &mut ApplyContext, idx: usize) -> bool; |
473 | } |
474 | |
475 | impl ValueRecordExt for ValueRecord<'_> { |
476 | fn apply(&self, ctx: &mut ApplyContext, idx: usize) -> bool { |
477 | let horizontal = ctx.buffer.direction.is_horizontal(); |
478 | let mut pos = ctx.buffer.pos[idx]; |
479 | let mut worked = false; |
480 | |
481 | if self.x_placement != 0 { |
482 | pos.x_offset += i32::from(self.x_placement); |
483 | worked = true; |
484 | } |
485 | |
486 | if self.y_placement != 0 { |
487 | pos.y_offset += i32::from(self.y_placement); |
488 | worked = true; |
489 | } |
490 | |
491 | if self.x_advance != 0 && horizontal { |
492 | pos.x_advance += i32::from(self.x_advance); |
493 | worked = true; |
494 | } |
495 | |
496 | if self.y_advance != 0 && !horizontal { |
497 | // y_advance values grow downward but font-space grows upward, hence negation |
498 | pos.y_advance -= i32::from(self.y_advance); |
499 | worked = true; |
500 | } |
501 | |
502 | { |
503 | let (ppem_x, ppem_y) = ctx.face.pixels_per_em().unwrap_or((0, 0)); |
504 | let coords = ctx.face.ttfp_face.variation_coordinates().len(); |
505 | let use_x_device = ppem_x != 0 || coords != 0; |
506 | let use_y_device = ppem_y != 0 || coords != 0; |
507 | |
508 | if use_x_device { |
509 | if let Some(device) = self.x_placement_device { |
510 | pos.x_offset += device.get_x_delta(ctx.face).unwrap_or(0); |
511 | worked = true; // TODO: even when 0? |
512 | } |
513 | } |
514 | |
515 | if use_y_device { |
516 | if let Some(device) = self.y_placement_device { |
517 | pos.y_offset += device.get_y_delta(ctx.face).unwrap_or(0); |
518 | worked = true; |
519 | } |
520 | } |
521 | |
522 | if horizontal && use_x_device { |
523 | if let Some(device) = self.x_advance_device { |
524 | pos.x_advance += device.get_x_delta(ctx.face).unwrap_or(0); |
525 | worked = true; |
526 | } |
527 | } |
528 | |
529 | if !horizontal && use_y_device { |
530 | if let Some(device) = self.y_advance_device { |
531 | // y_advance values grow downward but face-space grows upward, hence negation |
532 | pos.y_advance -= device.get_y_delta(ctx.face).unwrap_or(0); |
533 | worked = true; |
534 | } |
535 | } |
536 | } |
537 | |
538 | ctx.buffer.pos[idx] = pos; |
539 | worked |
540 | } |
541 | } |
542 | |
543 | trait MarkArrayExt { |
544 | fn apply( |
545 | &self, |
546 | ctx: &mut ApplyContext, |
547 | anchors: AnchorMatrix, |
548 | mark_index: u16, |
549 | glyph_index: u16, |
550 | glyph_pos: usize, |
551 | ) -> Option<()>; |
552 | } |
553 | |
554 | impl MarkArrayExt for MarkArray<'_> { |
555 | fn apply( |
556 | &self, |
557 | ctx: &mut ApplyContext, |
558 | anchors: AnchorMatrix, |
559 | mark_index: u16, |
560 | glyph_index: u16, |
561 | glyph_pos: usize, |
562 | ) -> Option<()> { |
563 | // If this subtable doesn't have an anchor for this base and this class |
564 | // return `None` such that the subsequent subtables have a chance at it. |
565 | let (mark_class, mark_anchor) = self.get(mark_index)?; |
566 | let base_anchor = anchors.get(glyph_index, mark_class)?; |
567 | |
568 | let (mark_x, mark_y) = mark_anchor.get(ctx.face); |
569 | let (base_x, base_y) = base_anchor.get(ctx.face); |
570 | |
571 | ctx.buffer.unsafe_to_break(glyph_pos, ctx.buffer.idx); |
572 | |
573 | let idx = ctx.buffer.idx; |
574 | let pos = ctx.buffer.cur_pos_mut(); |
575 | pos.x_offset = base_x - mark_x; |
576 | pos.y_offset = base_y - mark_y; |
577 | pos.set_attach_type(attach_type::MARK); |
578 | pos.set_attach_chain((glyph_pos as isize - idx as isize) as i16); |
579 | |
580 | ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
581 | ctx.buffer.idx += 1; |
582 | |
583 | Some(()) |
584 | } |
585 | } |
586 | |
587 | pub mod attach_type { |
588 | pub const MARK: u8 = 1; |
589 | pub const CURSIVE: u8 = 2; |
590 | } |
591 | |
592 | /// Just like TryFrom<N>, but for numeric types not supported by the Rust's std. |
593 | pub trait TryNumFrom<T>: Sized { |
594 | /// Casts between numeric types. |
595 | fn try_num_from(_: T) -> Option<Self>; |
596 | } |
597 | |
598 | impl TryNumFrom<f32> for i32 { |
599 | #[inline ] |
600 | fn try_num_from(v: f32) -> Option<Self> { |
601 | // Based on https://github.com/rust-num/num-traits/blob/master/src/cast.rs |
602 | |
603 | // Float as int truncates toward zero, so we want to allow values |
604 | // in the exclusive range `(MIN-1, MAX+1)`. |
605 | |
606 | // We can't represent `MIN-1` exactly, but there's no fractional part |
607 | // at this magnitude, so we can just use a `MIN` inclusive boundary. |
608 | const MIN: f32 = core::i32::MIN as f32; |
609 | // We can't represent `MAX` exactly, but it will round up to exactly |
610 | // `MAX+1` (a power of two) when we cast it. |
611 | const MAX_P1: f32 = core::i32::MAX as f32; |
612 | if v >= MIN && v < MAX_P1 { |
613 | Some(v as i32) |
614 | } else { |
615 | None |
616 | } |
617 | } |
618 | } |
619 | |
620 | trait DeviceExt { |
621 | fn get_x_delta(&self, face: &Face) -> Option<i32>; |
622 | fn get_y_delta(&self, face: &Face) -> Option<i32>; |
623 | } |
624 | |
625 | impl DeviceExt for Device<'_> { |
626 | fn get_x_delta(&self, face: &Face) -> Option<i32> { |
627 | match self { |
628 | Device::Hinting(hinting) => hinting.x_delta(face.units_per_em, face.pixels_per_em()), |
629 | Device::Variation(variation) => face |
630 | .tables() |
631 | .gdef? |
632 | .glyph_variation_delta( |
633 | variation.outer_index, |
634 | variation.inner_index, |
635 | face.variation_coordinates(), |
636 | ) |
637 | .and_then(|float| i32::try_num_from(crate::round(float))), |
638 | } |
639 | } |
640 | |
641 | fn get_y_delta(&self, face: &Face) -> Option<i32> { |
642 | match self { |
643 | Device::Hinting(hinting) => hinting.y_delta(face.units_per_em, face.pixels_per_em()), |
644 | Device::Variation(variation) => face |
645 | .tables() |
646 | .gdef? |
647 | .glyph_variation_delta( |
648 | variation.outer_index, |
649 | variation.inner_index, |
650 | face.variation_coordinates(), |
651 | ) |
652 | .and_then(|float| i32::try_num_from(crate::round(float))), |
653 | } |
654 | } |
655 | } |
656 | |
657 | trait AnchorExt { |
658 | fn get(&self, face: &Face) -> (i32, i32); |
659 | } |
660 | |
661 | impl AnchorExt for Anchor<'_> { |
662 | fn get(&self, face: &Face) -> (i32, i32) { |
663 | let mut x = i32::from(self.x); |
664 | let mut y = i32::from(self.y); |
665 | |
666 | if self.x_device.is_some() || self.y_device.is_some() { |
667 | let (ppem_x, ppem_y) = face.pixels_per_em().unwrap_or((0, 0)); |
668 | let coords = face.ttfp_face.variation_coordinates().len(); |
669 | |
670 | if let Some(device) = self.x_device { |
671 | if ppem_x != 0 || coords != 0 { |
672 | x += device.get_x_delta(face).unwrap_or(0); |
673 | } |
674 | } |
675 | |
676 | if let Some(device) = self.y_device { |
677 | if ppem_y != 0 || coords != 0 { |
678 | y += device.get_y_delta(face).unwrap_or(0); |
679 | } |
680 | } |
681 | } |
682 | |
683 | (x, y) |
684 | } |
685 | } |
686 | |