1use crate::fonts::{FONT_REF_LEN, FONT_REF_SYSTEM, FontRefs};
22use crate::protocol::ProtocolError;
23use crate::schema::{OverflowWrap, TypeRole, WordBreak};
24use crate::spans::{SPAN_FONT_ENTRY_LEN, SPAN_FONT_OFFSET_AT, SPAN_LEN, TextSpans};
25use std::marker::PhantomData;
26
27pub const MEASURE_RECORD_LEN: usize = 72;
29pub const MEASURE_RESULT_LEN: usize = 32;
31
32pub const MEASURE_TEXT: u16 = 1;
34pub const MEASURE_NODE: u16 = 2;
36
37pub const MEASURE_KIND_AT: usize = 0;
39
40pub const TEXT_TYPE_ROLE_AT: usize = 2;
41pub const TEXT_TEXT_OFFSET_AT: usize = 4;
42pub const TEXT_TEXT_LENGTH_AT: usize = 8;
43pub const TEXT_SPANS_OFFSET_AT: usize = 12;
44pub const TEXT_SPANS_COUNT_AT: usize = 16;
45pub const TEXT_SPAN_FONTS_OFFSET_AT: usize = 20;
46pub const TEXT_SPAN_FONTS_COUNT_AT: usize = 24;
47pub const TEXT_FONT_OFFSET_AT: usize = 28;
48pub const TEXT_FONT_COUNT_AT: usize = 32;
49pub const TEXT_FONT_SIZE_AT: usize = 36;
50pub const TEXT_FONT_WEIGHT_AT: usize = 40;
51pub const TEXT_ITALIC_AT: usize = 42;
52pub const TEXT_WRAP_AT: usize = 43;
53pub const TEXT_LETTER_SPACING_AT: usize = 44;
54pub const TEXT_LINE_HEIGHT_AT: usize = 48;
55pub const TEXT_MAX_LINES_AT: usize = 52;
56pub const TEXT_TAB_SIZE_AT: usize = 56;
57pub const TEXT_WORD_BREAK_AT: usize = 57;
58pub const TEXT_OVERFLOW_WRAP_AT: usize = 58;
59pub const TEXT_ABSOLUTE_SIZE_AT: usize = 59;
60pub const TEXT_CONSTRAINT_AT: usize = 60;
61pub const TEXT_WIDTH_AT: usize = 64;
62pub const TEXT_ZOOM_AT: usize = 68;
63
64pub const NODE_ID_AT: usize = 4;
65pub const NODE_MIN_WIDTH_AT: usize = 8;
66pub const NODE_MAX_WIDTH_AT: usize = 12;
67pub const NODE_MIN_HEIGHT_AT: usize = 16;
68pub const NODE_MAX_HEIGHT_AT: usize = 20;
69pub const NODE_ZOOM_AT: usize = 24;
70
71pub const CONSTRAINT_MIN_CONTENT: u32 = 1;
73pub const CONSTRAINT_MAX_CONTENT: u32 = 2;
75pub const CONSTRAINT_AT_MOST: u32 = 3;
77
78pub const RESULT_WIDTH_AT: usize = 0;
80pub const RESULT_HEIGHT_AT: usize = 4;
81pub const RESULT_FIRST_BASELINE_AT: usize = 8;
82pub const RESULT_LAST_BASELINE_AT: usize = 12;
83pub const RESULT_LAST_LINE_WIDTH_AT: usize = 16;
84pub const RESULT_LINE_COUNT_AT: usize = 20;
85pub const RESULT_FLAGS_AT: usize = 24;
86pub const RESULT_STATUS_AT: usize = 28;
87
88pub const FLAG_TRUNCATED: u32 = 1;
90
91pub const RESULT_OK: u32 = 0;
92pub const RESULT_UNKNOWN_NODE: u32 = 1;
95pub const RESULT_MALFORMED: u32 = 2;
97
98pub const MEASURE_UNREADABLE: i32 = -1;
100pub const MEASURE_OFF_UI_THREAD: i32 = -4;
103pub const MEASURE_UNAVAILABLE: i32 = -5;
107
108pub(crate) const SCHEMA_DESCRIPTOR: &str = concat!(
111 "measure=v1;record=72;result=32;",
112 "text=kind@0,type_role@2,text@4,spans@12,span_fonts@20,font@28,font_size@36,",
113 "font_weight@40,italic@42,wrap@43,letter_spacing@44,line_height@48,max_lines@52,",
114 "tab_size@56,word_break@57,overflow_wrap@58,absolute_size@59,constraint@60,width@64,",
115 "zoom@68;",
116 "node=kind@0,node@4,min_width@8,max_width@12,min_height@16,max_height@20,zoom@24;",
117 "result=width@0,height@4,first_baseline@8,last_baseline@12,last_line_width@16,",
118 "line_count@20,flags@24,status@28;",
119 "fontref=12,kind@0,value@4,length@8;spanfont=12,span@0,offset@4,count@8;",
120);
121
122#[repr(C)]
124#[derive(Clone, Copy, Debug, Default, PartialEq)]
125pub struct MeasureResult {
126 pub width: f32,
127 pub height: f32,
128 pub first_baseline: f32,
130 pub last_baseline: f32,
131 pub last_line_width: f32,
134 pub line_count: u32,
136 pub flags: u32,
137 pub status: u32,
138}
139
140const _: () = assert!(std::mem::size_of::<MeasureResult>() == MEASURE_RESULT_LEN);
141
142impl MeasureResult {
143 pub const REFUSED: Self = Self {
146 width: 0.0,
147 height: 0.0,
148 first_baseline: f32::NAN,
149 last_baseline: f32::NAN,
150 last_line_width: f32::NAN,
151 line_count: 0,
152 flags: 0,
153 status: RESULT_MALFORMED,
154 };
155
156 pub fn status(&self) -> MeasureStatus {
157 match self.status {
158 RESULT_OK => MeasureStatus::Ok,
159 RESULT_UNKNOWN_NODE => MeasureStatus::UnknownNode,
160 RESULT_MALFORMED => MeasureStatus::Malformed,
161 other => MeasureStatus::Other(other),
162 }
163 }
164
165 pub fn truncated(&self) -> bool {
167 self.flags & FLAG_TRUNCATED != 0
168 }
169}
170
171#[derive(Clone, Copy, Debug, Eq, PartialEq)]
173pub enum MeasureStatus {
174 Ok,
175 UnknownNode,
176 Malformed,
177 Other(u32),
179}
180
181#[derive(Clone, Copy, Debug, PartialEq)]
183pub enum TextConstraint {
184 MinContent,
186 MaxContent,
188 AtMost(f32),
190}
191
192#[derive(Clone, Debug, PartialEq)]
198pub struct TextStyle {
199 pub type_role: TypeRole,
200 pub font: FontRefs,
202 pub font_size: Option<f32>,
204 pub font_weight: Option<u16>,
206 pub italic: bool,
207 pub letter_spacing: Option<f32>,
209 pub line_height: Option<f32>,
211 pub max_lines: Option<u32>,
213 pub wrap: bool,
215 pub tab_size: u8,
217 pub word_break: Option<WordBreak>,
218 pub overflow_wrap: Option<OverflowWrap>,
219 pub absolute_size: bool,
221}
222
223impl Default for TextStyle {
224 fn default() -> Self {
225 Self {
226 type_role: TypeRole::Body,
227 font: FontRefs::default(),
228 font_size: None,
229 font_weight: None,
230 italic: false,
231 letter_spacing: None,
232 line_height: None,
233 max_lines: None,
234 wrap: true,
235 tab_size: 8,
236 word_break: None,
237 overflow_wrap: None,
238 absolute_size: false,
239 }
240 }
241}
242
243impl TextStyle {
244 pub fn role(role: TypeRole) -> Self {
246 Self {
247 type_role: role,
248 ..Self::default()
249 }
250 }
251}
252
253#[derive(Clone, Copy, Debug, PartialEq)]
255pub struct NodeConstraints {
256 pub min_width: f32,
257 pub max_width: f32,
258 pub min_height: f32,
259 pub max_height: f32,
260}
261
262impl NodeConstraints {
263 pub const UNBOUNDED: Self = Self {
265 min_width: 0.0,
266 max_width: f32::INFINITY,
267 min_height: 0.0,
268 max_height: f32::INFINITY,
269 };
270
271 pub const fn width(width: f32) -> Self {
273 Self {
274 min_width: width,
275 max_width: width,
276 min_height: 0.0,
277 max_height: f32::INFINITY,
278 }
279 }
280}
281
282#[derive(Clone, Copy, Debug)]
284struct PendingRecord {
285 bytes: [u8; MEASURE_RECORD_LEN],
286}
287
288#[derive(Clone, Copy)]
290struct SideTable {
291 entry_len: usize,
292 list_offset_at: usize,
293 list_count_at: usize,
294}
295
296#[derive(Debug, Default)]
301pub struct MeasureRequests {
302 records: Vec<PendingRecord>,
303 payload: Vec<u8>,
306 payload_patches: Vec<u32>,
309 buffer: Vec<u8>,
311 zoom: Option<f32>,
313}
314
315impl MeasureRequests {
316 pub fn new() -> Self {
317 Self::default()
318 }
319
320 pub fn set_zoom(&mut self, zoom: f32) {
328 self.zoom = Some(zoom);
329 }
330
331 pub fn clear(&mut self) {
333 self.records.clear();
334 self.payload.clear();
335 self.payload_patches.clear();
336 self.buffer.clear();
337 }
338
339 pub fn len(&self) -> usize {
341 self.records.len()
342 }
343
344 pub fn is_empty(&self) -> bool {
345 self.records.is_empty()
346 }
347
348 pub fn text(
351 &mut self,
352 text: &str,
353 spans: &TextSpans,
354 style: &TextStyle,
355 constraint: TextConstraint,
356 ) -> usize {
357 let mut record = [0u8; MEASURE_RECORD_LEN];
358 put_u16(&mut record, MEASURE_KIND_AT, MEASURE_TEXT);
359 put_u16(&mut record, TEXT_TYPE_ROLE_AT, style.type_role as u16);
360
361 let text_at = self.payload.len() as u32;
362 self.payload.extend_from_slice(text.as_bytes());
363 put_u32(&mut record, TEXT_TEXT_OFFSET_AT, text_at);
364 put_u32(&mut record, TEXT_TEXT_LENGTH_AT, text.len() as u32);
365
366 let runs = spans.as_bytes();
367 let spans_at = self.payload.len() as u32;
368 self.payload.extend_from_slice(runs);
369 put_u32(&mut record, TEXT_SPANS_OFFSET_AT, spans_at);
370 put_u32(
371 &mut record,
372 TEXT_SPANS_COUNT_AT,
373 (runs.len() / SPAN_LEN) as u32,
374 );
375
376 let (entries_at, entries) = self.copy_span_font_table(spans.font_table());
379 put_u32(&mut record, TEXT_SPAN_FONTS_OFFSET_AT, entries_at);
380 put_u32(&mut record, TEXT_SPAN_FONTS_COUNT_AT, entries);
381 let (font_at, fonts) = self.copy_font_list(style.font.as_bytes());
382 put_u32(&mut record, TEXT_FONT_OFFSET_AT, font_at);
383 put_u32(&mut record, TEXT_FONT_COUNT_AT, fonts);
384
385 put_f32(
386 &mut record,
387 TEXT_FONT_SIZE_AT,
388 style.font_size.unwrap_or(0.0),
389 );
390 put_u16(
391 &mut record,
392 TEXT_FONT_WEIGHT_AT,
393 style.font_weight.unwrap_or(0),
394 );
395 record[TEXT_ITALIC_AT] = u8::from(style.italic);
396 record[TEXT_WRAP_AT] = u8::from(style.wrap);
397 put_f32(
398 &mut record,
399 TEXT_LETTER_SPACING_AT,
400 style.letter_spacing.unwrap_or(f32::NAN),
401 );
402 put_f32(
403 &mut record,
404 TEXT_LINE_HEIGHT_AT,
405 style.line_height.unwrap_or(f32::NAN),
406 );
407 put_u32(&mut record, TEXT_MAX_LINES_AT, style.max_lines.unwrap_or(0));
408 record[TEXT_TAB_SIZE_AT] = style.tab_size;
409 record[TEXT_WORD_BREAK_AT] = style.word_break.map_or(0, |value| value as u8);
410 record[TEXT_OVERFLOW_WRAP_AT] = style.overflow_wrap.map_or(0, |value| value as u8);
411 record[TEXT_ABSOLUTE_SIZE_AT] = u8::from(style.absolute_size);
412 let (constraint, width) = match constraint {
413 TextConstraint::MinContent => (CONSTRAINT_MIN_CONTENT, 0.0),
414 TextConstraint::MaxContent => (CONSTRAINT_MAX_CONTENT, 0.0),
415 TextConstraint::AtMost(width) => (CONSTRAINT_AT_MOST, width),
416 };
417 put_u32(&mut record, TEXT_CONSTRAINT_AT, constraint);
418 put_f32(&mut record, TEXT_WIDTH_AT, width);
419 put_f32(&mut record, TEXT_ZOOM_AT, self.zoom.unwrap_or(0.0));
420 self.records.push(PendingRecord { bytes: record });
421 self.records.len() - 1
422 }
423
424 pub fn node(&mut self, node_id: u32, constraints: NodeConstraints) -> usize {
427 let mut record = [0u8; MEASURE_RECORD_LEN];
428 put_u16(&mut record, MEASURE_KIND_AT, MEASURE_NODE);
429 put_u32(&mut record, NODE_ID_AT, node_id);
430 put_f32(&mut record, NODE_MIN_WIDTH_AT, constraints.min_width);
431 put_f32(&mut record, NODE_MAX_WIDTH_AT, constraints.max_width);
432 put_f32(&mut record, NODE_MIN_HEIGHT_AT, constraints.min_height);
433 put_f32(&mut record, NODE_MAX_HEIGHT_AT, constraints.max_height);
434 put_f32(&mut record, NODE_ZOOM_AT, self.zoom.unwrap_or(0.0));
435 self.records.push(PendingRecord { bytes: record });
436 self.records.len() - 1
437 }
438
439 fn copy_font_list(&mut self, blob: &[u8]) -> (u32, u32) {
443 let at = self.payload.len();
444 if blob.len() < 4 {
445 return (at as u32, 0);
446 }
447 let count = read_u32(blob, 0);
448 self.payload.extend_from_slice(&blob[4..]);
449 let moved = (at as u32).wrapping_sub(4);
451 self.move_font_records(at, count, moved);
452 (at as u32, count)
453 }
454
455 fn copy_span_font_table(&mut self, table: &[u8]) -> (u32, u32) {
458 self.copy_side_table(
459 table,
460 SideTable {
461 entry_len: SPAN_FONT_ENTRY_LEN,
462 list_offset_at: SPAN_FONT_OFFSET_AT,
463 list_count_at: 8,
464 },
465 )
466 }
467
468 fn copy_side_table(&mut self, table: &[u8], layout: SideTable) -> (u32, u32) {
474 let at = self.payload.len();
475 if table.len() < 4 {
476 return (at as u32, 0);
477 }
478 let entries = read_u32(table, 0);
479 self.payload.extend_from_slice(&table[4..]);
480 let moved = (at as u32).wrapping_sub(4);
481 for entry in 0..entries as usize {
482 let start = at + entry * layout.entry_len;
483 let field = start + layout.list_offset_at;
484 if field + 4 > self.payload.len() {
485 break;
486 }
487 let list = read_u32(&self.payload, field).wrapping_add(moved);
488 self.payload[field..field + 4].copy_from_slice(&list.to_le_bytes());
489 self.payload_patches.push(field as u32);
490 let count = read_u32(&self.payload, start + layout.list_count_at);
491 self.move_font_records(list as usize, count, moved);
492 }
493 (at as u32, entries)
494 }
495
496 fn move_font_records(&mut self, at: usize, count: u32, moved: u32) {
499 for index in 0..count as usize {
500 let record = at + index * FONT_REF_LEN;
501 if record + FONT_REF_LEN > self.payload.len() {
502 break;
503 }
504 if read_u32(&self.payload, record) != FONT_REF_SYSTEM {
505 continue;
506 }
507 let field = record + 4;
508 let name = read_u32(&self.payload, field).wrapping_add(moved);
509 self.payload[field..field + 4].copy_from_slice(&name.to_le_bytes());
510 self.payload_patches.push(field as u32);
511 }
512 }
513
514 pub fn encoded(&mut self) -> &[u8] {
517 self.finish().0
518 }
519
520 fn finish(&mut self) -> (&[u8], u32) {
523 let base = (self.records.len() * MEASURE_RECORD_LEN) as u32;
524 self.buffer.clear();
525 self.buffer.reserve(base as usize + self.payload.len());
526 for record in &self.records {
527 let mut bytes = record.bytes;
528 if u16::from_le_bytes([bytes[0], bytes[1]]) == MEASURE_TEXT {
529 for field in [
530 TEXT_TEXT_OFFSET_AT,
531 TEXT_SPANS_OFFSET_AT,
532 TEXT_SPAN_FONTS_OFFSET_AT,
533 TEXT_FONT_OFFSET_AT,
534 ] {
535 let value = read_u32(&bytes, field) + base;
536 put_u32(&mut bytes, field, value);
537 }
538 }
539 self.buffer.extend_from_slice(&bytes);
540 }
541 let payload_at = self.buffer.len();
542 self.buffer.extend_from_slice(&self.payload);
543 for &patch in &self.payload_patches {
544 let at = payload_at + patch as usize;
545 let value = read_u32(&self.buffer, at) + base;
546 self.buffer[at..at + 4].copy_from_slice(&value.to_le_bytes());
547 }
548 (&self.buffer, self.records.len() as u32)
549 }
550}
551
552pub struct Measurer {
557 _on_this_thread: PhantomData<*const ()>,
558}
559
560#[derive(Clone, Debug, Eq, PartialEq)]
562pub enum MeasureError {
563 OutsideHostCall,
565 Refused(ProtocolError),
568}
569
570pub fn measurer() -> Result<Measurer, MeasureError> {
573 if crate::boundary::in_host_call() {
574 Ok(Measurer {
575 _on_this_thread: PhantomData,
576 })
577 } else {
578 Err(MeasureError::OutsideHostCall)
579 }
580}
581
582impl Measurer {
583 pub fn measure(
591 &mut self,
592 requests: &mut MeasureRequests,
593 results: &mut Vec<MeasureResult>,
594 ) -> Result<(), MeasureError> {
595 let (buffer, count) = requests.finish();
596 results.clear();
597 results.resize(count as usize, MeasureResult::default());
598 self.measure_encoded(buffer, count, results)
599 }
600
601 pub fn measure_encoded(
604 &mut self,
605 buffer: &[u8],
606 count: u32,
607 results: &mut [MeasureResult],
608 ) -> Result<(), MeasureError> {
609 if results.len() < count as usize {
610 return Err(MeasureError::Refused(ProtocolError::LengthOverflow));
611 }
612 if count == 0 {
613 return Ok(());
614 }
615 let length = u32::try_from(buffer.len())
616 .map_err(|_| MeasureError::Refused(ProtocolError::LengthOverflow))?;
617 let status =
618 crate::boundary::renderer_measure(buffer.as_ptr(), length, count, results.as_mut_ptr());
619 if status < 0 {
620 for result in results.iter_mut().take(count as usize) {
621 *result = MeasureResult::REFUSED;
622 }
623 report_refusal(status);
624 return Err(MeasureError::Refused(ProtocolError::MeasureRefused(status)));
625 }
626 Ok(())
627 }
628}
629
630fn report_refusal(status: i32) {
634 use std::sync::atomic::{AtomicU32, Ordering};
635 static SAID: AtomicU32 = AtomicU32::new(0);
636 let bit = 1u32 << (status.unsigned_abs().min(31));
637 if SAID.fetch_or(bit, Ordering::Relaxed) & bit != 0 {
638 return;
639 }
640 let reason = match status {
641 MEASURE_UNREADABLE => "the request buffer could not be read",
642 MEASURE_OFF_UI_THREAD => "the call was made off the Renderer's UI thread",
643 MEASURE_UNAVAILABLE => {
644 "the Renderer has nothing to measure with yet (no first frame, or no renderer)"
645 }
646 _ => "the Renderer gave no reason",
647 };
648 eprintln!(
649 "compose-rust: a measure call was refused with status {status}: {reason}. Every \
650 size in it is zero and the layout carried on."
651 );
652}
653
654fn put_u16(bytes: &mut [u8], at: usize, value: u16) {
655 bytes[at..at + 2].copy_from_slice(&value.to_le_bytes());
656}
657
658fn put_u32(bytes: &mut [u8], at: usize, value: u32) {
659 bytes[at..at + 4].copy_from_slice(&value.to_le_bytes());
660}
661
662fn put_f32(bytes: &mut [u8], at: usize, value: f32) {
663 bytes[at..at + 4].copy_from_slice(&value.to_bits().to_le_bytes());
664}
665
666fn read_u32(bytes: &[u8], at: usize) -> u32 {
667 u32::from_le_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]])
668}
669
670pub const FAKE_FONT_SIZE: f32 = 14.0;
681
682#[doc(hidden)]
690pub unsafe extern "C" fn fake_measure(
691 requests: *const u8,
692 length: u32,
693 count: u32,
694 results: *mut MeasureResult,
695) -> i32 {
696 if !crate::boundary::in_host_call() {
697 return MEASURE_OFF_UI_THREAD;
698 }
699 if requests.is_null() || results.is_null() {
700 return MEASURE_UNREADABLE;
701 }
702 let buffer = unsafe { std::slice::from_raw_parts(requests, length as usize) };
704 let results = unsafe { std::slice::from_raw_parts_mut(results, count as usize) };
706 fake_measure_slices(buffer, results)
707}
708
709#[doc(hidden)]
711pub fn fake_measure_slices(buffer: &[u8], results: &mut [MeasureResult]) -> i32 {
712 let count = results.len();
713 if buffer.len() < count * MEASURE_RECORD_LEN {
714 return MEASURE_UNREADABLE;
715 }
716 for (index, result) in results.iter_mut().enumerate() {
717 let record = &buffer[index * MEASURE_RECORD_LEN..(index + 1) * MEASURE_RECORD_LEN];
718 *result = fake_one(buffer, record);
719 }
720 0
721}
722
723fn fake_one(buffer: &[u8], record: &[u8]) -> MeasureResult {
724 let malformed = MeasureResult {
725 status: RESULT_MALFORMED,
726 ..MeasureResult::REFUSED
727 };
728 match u16::from_le_bytes([record[0], record[1]]) {
729 MEASURE_NODE => MeasureResult {
730 status: RESULT_UNKNOWN_NODE,
731 ..MeasureResult::REFUSED
732 },
733 MEASURE_TEXT => {
734 let at = read_u32(record, TEXT_TEXT_OFFSET_AT) as usize;
735 let length = read_u32(record, TEXT_TEXT_LENGTH_AT) as usize;
736 let Some(text) = at
737 .checked_add(length)
738 .and_then(|end| buffer.get(at..end))
739 .and_then(|bytes| std::str::from_utf8(bytes).ok())
740 else {
741 return malformed;
742 };
743 let size = f32::from_bits(read_u32(record, TEXT_FONT_SIZE_AT));
744 let size = if size > 0.0 { size } else { FAKE_FONT_SIZE };
745 let line_height = f32::from_bits(read_u32(record, TEXT_LINE_HEIGHT_AT));
746 let line_height = if line_height.is_nan() || line_height <= 0.0 {
747 size * 1.25
748 } else {
749 line_height
750 };
751 let max_lines = read_u32(record, TEXT_MAX_LINES_AT);
752 let wrap = record[TEXT_WRAP_AT] != 0;
753 let advance = size * 0.5;
754 let width = f32::from_bits(read_u32(record, TEXT_WIDTH_AT));
755 let lines = match read_u32(record, TEXT_CONSTRAINT_AT) {
756 CONSTRAINT_MIN_CONTENT => {
757 fake_lines(text, if wrap { 0.0 } else { f32::INFINITY }, advance)
758 }
759 CONSTRAINT_MAX_CONTENT => fake_lines(text, f32::INFINITY, advance),
760 CONSTRAINT_AT_MOST if width >= 0.0 => {
761 fake_lines(text, if wrap { width } else { f32::INFINITY }, advance)
762 }
763 _ => return malformed,
764 };
765 let shown = if max_lines == 0 {
766 lines.len()
767 } else {
768 lines.len().min(max_lines as usize)
769 };
770 let widest = lines[..shown].iter().copied().fold(0.0f32, f32::max);
771 MeasureResult {
772 width: widest,
773 height: shown as f32 * line_height,
774 first_baseline: size,
775 last_baseline: (shown.max(1) - 1) as f32 * line_height + size,
776 last_line_width: lines[shown.max(1) - 1],
777 line_count: shown as u32,
778 flags: if shown < lines.len() {
779 FLAG_TRUNCATED
780 } else {
781 0
782 },
783 status: RESULT_OK,
784 }
785 }
786 _ => malformed,
787 }
788}
789
790fn fake_lines(text: &str, width: f32, advance: f32) -> Vec<f32> {
793 let mut lines = Vec::new();
794 for paragraph in text.split('\n') {
795 let mut line = 0usize;
796 for word in paragraph.split(' ') {
797 let word_length = word.chars().count();
798 let joined = if line == 0 {
799 word_length
800 } else {
801 line + 1 + word_length
802 };
803 if line != 0 && joined as f32 * advance > width {
804 lines.push(line as f32 * advance);
805 line = word_length;
806 } else {
807 line = joined;
808 }
809 }
810 lines.push(line as f32 * advance);
811 }
812 lines
813}
814
815#[cfg(test)]
816mod side_table_tests {
817 use super::*;
818
819 fn table() -> Vec<u8> {
822 let mut t = Vec::new();
823 t.extend_from_slice(&1u32.to_le_bytes());
824 t.extend_from_slice(&7u32.to_le_bytes());
825 t.extend_from_slice(&(4 + SPAN_FONT_ENTRY_LEN as u32).to_le_bytes());
826 t.extend_from_slice(&1u32.to_le_bytes());
827 let name_at = 4 + SPAN_FONT_ENTRY_LEN as u32 + FONT_REF_LEN as u32;
828 t.extend_from_slice(&FONT_REF_SYSTEM.to_le_bytes());
829 t.extend_from_slice(&name_at.to_le_bytes());
830 t.resize(4 + SPAN_FONT_ENTRY_LEN + FONT_REF_LEN, 0);
831 t.extend_from_slice(b"name");
832 t
833 }
834
835 #[test]
836 fn pr2_side_table_copy_moves_offsets_and_keeps_the_bytes() {
837 let mut requests = MeasureRequests::new();
838 requests.payload.extend_from_slice(&[0xAA; 10]);
839 let table = table();
840 let (at, entries) = requests.copy_span_font_table(&table);
841 assert_eq!((at, entries), (10, 1));
842 let moved = 10u32 - 4;
843 let copied = &requests.payload[10..];
844 assert_eq!(copied.len(), table.len() - 4);
845 assert_eq!(
846 read_u32(copied, 0),
847 7,
848 "fields that are not offsets are untouched"
849 );
850 assert_eq!(
851 read_u32(copied, SPAN_FONT_OFFSET_AT),
852 table_u32(&table, 4 + SPAN_FONT_OFFSET_AT) + moved
853 );
854 assert_eq!(read_u32(copied, 8), 1);
855 let record = SPAN_FONT_ENTRY_LEN;
856 assert_eq!(
857 read_u32(copied, record + 4),
858 table_u32(&table, 4 + record + 4) + moved
859 );
860 assert_eq!(&copied[copied.len() - 4..], b"name");
861 assert_eq!(requests.payload_patches.len(), 2);
862 assert_eq!(
863 requests.copy_span_font_table(&[]),
864 (requests.payload.len() as u32, 0)
865 );
866 }
867
868 fn table_u32(table: &[u8], at: usize) -> u32 {
869 read_u32(table, at)
870 }
871}