Skip to main content

compose_rust/
measure.rs

1//! Asking the Renderer how big something will be, while the Host is laying it out.
2//!
3//! A Host that lays out a page itself (a flexbox, a grid, text flowing around a picture)
4//! needs to know how wide a run of text is before it can decide where anything goes, and
5//! only the side that draws the letters can say: the font, the density, the system's font
6//! scale and the design system's type scale are all the Renderer's. So the Host asks, in
7//! the middle of its own work, and the Renderer answers on the same call stack.
8//!
9//! One call carries many questions. A sidebar lays out a couple of hundred labels, and a
10//! flexbox asks each of them for its narrowest width, its widest width and its height at a
11//! given width, so one call per question would pay the crossing six hundred times.
12//!
13//! The questions travel the way a batch does: fixed-length records first, then the strings
14//! and run lists they point at. The answers come back as fixed-length records in an array
15//! the Host owns. Nothing is serialised and nothing is copied on the way across.
16//!
17//! Application code does not call the boundary. A layout engine asks [`measurer`] for a
18//! [`Measurer`], which it gets only while the Host is inside one of its own calls, which is
19//! the only time the Renderer is standing still on the other end of the same thread.
20
21use 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
27/// Bytes of one request record, whichever kind it is.
28pub const MEASURE_RECORD_LEN: usize = 72;
29/// Bytes of one result record.
30pub const MEASURE_RESULT_LEN: usize = 32;
31
32/// A run of text, its style and a width constraint.
33pub const MEASURE_TEXT: u16 = 1;
34/// A node the Renderer has already applied, and size constraints.
35pub const MEASURE_NODE: u16 = 2;
36
37// Where each field of a request record starts. Both kinds start with the kind.
38pub 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
71/// The narrowest the text can be: its longest piece that cannot be broken.
72pub const CONSTRAINT_MIN_CONTENT: u32 = 1;
73/// The text with no line broken except where it says so.
74pub const CONSTRAINT_MAX_CONTENT: u32 = 2;
75/// The text broken into lines no wider than the given width.
76pub const CONSTRAINT_AT_MOST: u32 = 3;
77
78// Where each field of a result record starts.
79pub 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
88/// The text had more lines than `max_lines` allowed, and some were not shown.
89pub const FLAG_TRUNCATED: u32 = 1;
90
91pub const RESULT_OK: u32 = 0;
92/// The node is not in the Renderer's table: never sent, removed, or sent in the batch the
93/// Host is computing right now, which the Renderer applies only once the call returns.
94pub const RESULT_UNKNOWN_NODE: u32 = 1;
95/// The record points outside the buffer, or is a kind or value the Renderer does not know.
96pub const RESULT_MALFORMED: u32 = 2;
97
98/// The request buffer as a whole cannot be read: shorter than its records, or null.
99pub const MEASURE_UNREADABLE: i32 = -1;
100/// The call came from a thread other than the Renderer's UI thread, and nothing was
101/// measured. The same number `run` gives for the same mistake.
102pub const MEASURE_OFF_UI_THREAD: i32 = -4;
103/// There is nothing to measure with yet: the Renderer has not composed its first frame,
104/// so it has no density, no fonts and no design system. Also what a build with no
105/// renderer answers.
106pub const MEASURE_UNAVAILABLE: i32 = -5;
107
108/// Hash input for the record layouts above, so a Renderer generated from another layout
109/// is refused at the handshake rather than reading the wrong bytes.
110pub(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/// The Renderer's answer to one request, in dp.
123#[repr(C)]
124#[derive(Clone, Copy, Debug, Default, PartialEq)]
125pub struct MeasureResult {
126    pub width: f32,
127    pub height: f32,
128    /// NaN where there is no baseline.
129    pub first_baseline: f32,
130    pub last_baseline: f32,
131    /// How wide the last line is, which is where inline content after the text starts.
132    /// NaN for a node.
133    pub last_line_width: f32,
134    /// Zero for a node.
135    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    /// What a refused call leaves in every slot: no size, and a status that says it was
144    /// not measured.
145    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    /// Whether `max_lines` cut the text short.
166    pub fn truncated(&self) -> bool {
167        self.flags & FLAG_TRUNCATED != 0
168    }
169}
170
171/// How one request went.
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
173pub enum MeasureStatus {
174    Ok,
175    UnknownNode,
176    Malformed,
177    /// A status this build does not know, from a Renderer newer than it.
178    Other(u32),
179}
180
181/// How wide the text may be.
182#[derive(Clone, Copy, Debug, PartialEq)]
183pub enum TextConstraint {
184    /// The narrowest it can be: CSS `min-content`.
185    MinContent,
186    /// As wide as it is with no line broken except where it says so: CSS `max-content`.
187    MaxContent,
188    /// Lines broken to fit this many dp.
189    AtMost(f32),
190}
191
192/// How a run of text is set, as the Renderer reads it.
193///
194/// The same fields a `Text` carries, meaning the same things: the Renderer resolves both
195/// with one function, so text measured with a style is the size the same text drawn with
196/// the same properties turns out to be.
197#[derive(Clone, Debug, PartialEq)]
198pub struct TextStyle {
199    pub type_role: TypeRole,
200    /// The fonts to try, for text with no role. Ignored where there is a role.
201    pub font: FontRefs,
202    /// `None` takes the role's size, or 14 for text with no role.
203    pub font_size: Option<f32>,
204    /// `None` takes the role's weight, or 400 for text with no role.
205    pub font_weight: Option<u16>,
206    pub italic: bool,
207    /// `None` takes the role's spacing, or none for text with no role.
208    pub letter_spacing: Option<f32>,
209    /// `None` is the font's own line height.
210    pub line_height: Option<f32>,
211    /// `None` is no limit.
212    pub max_lines: Option<u32>,
213    /// Whether lines break at the width given. `false` is CSS `nowrap` and `pre`.
214    pub wrap: bool,
215    /// How many spaces' width apart the tab stops are.
216    pub tab_size: u8,
217    pub word_break: Option<WordBreak>,
218    pub overflow_wrap: Option<OverflowWrap>,
219    /// Sizes are CSS pixels: the system's font scale does not enlarge them.
220    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    /// Text set by the design system's rung `role`.
245    pub fn role(role: TypeRole) -> Self {
246        Self {
247            type_role: role,
248            ..Self::default()
249        }
250    }
251}
252
253/// The size a node may take, in dp. `f32::INFINITY` is no limit.
254#[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    /// Any size at all.
264    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    /// Exactly this width, any height.
272    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/// The fields of one record, kept until the buffer is laid out.
283#[derive(Clone, Copy, Debug)]
284struct PendingRecord {
285    bytes: [u8; MEASURE_RECORD_LEN],
286}
287
288/// Where an entry of a side table keeps the list it points at.
289#[derive(Clone, Copy)]
290struct SideTable {
291    entry_len: usize,
292    list_offset_at: usize,
293    list_count_at: usize,
294}
295
296/// A set of questions for one call, built up and then asked at once.
297///
298/// Reused across frames: clearing keeps every buffer's capacity, so once warm, asking the
299/// same number of questions allocates nothing.
300#[derive(Debug, Default)]
301pub struct MeasureRequests {
302    records: Vec<PendingRecord>,
303    /// The strings and lists the records point at, counted from the start of this area
304    /// until the buffer is laid out.
305    payload: Vec<u8>,
306    /// Payload positions holding an offset that is counted from the start of the payload
307    /// and has to be counted from the start of the buffer instead.
308    payload_patches: Vec<u32>,
309    /// The laid out buffer.
310    buffer: Vec<u8>,
311    /// The zoom the requests asked from here on are measured at, where it was set.
312    zoom: Option<f32>,
313}
314
315impl MeasureRequests {
316    pub fn new() -> Self {
317        Self::default()
318    }
319
320    /// Measures the requests asked from here on at zoom `zoom`: the zoom of the page
321    /// region they belong to, which is the operating system's text size times the
322    /// application's own zoom. The Renderer measures them at its density times `zoom`,
323    /// with no font scale of its own, and answers in the region's CSS pixels.
324    ///
325    /// A Host can hold several regions at different zooms, which is why this travels with
326    /// every request rather than being the Renderer's state. Never set, it is one.
327    pub fn set_zoom(&mut self, zoom: f32) {
328        self.zoom = Some(zoom);
329    }
330
331    /// Forgets every question, keeping the room they took.
332    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    /// How many questions there are.
340    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    /// Asks how big `text`, with `spans` laid over it and set in `style`, is under
349    /// `constraint`. Returns the index its answer will have.
350    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        // Both font tables are copied as they are, records and names together, and only the
377        // offsets inside them are moved: a request costs no allocation once warm.
378        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    /// Asks how big the node `node_id`, which the Renderer has already applied, is under
425    /// `constraints`. Returns the index its answer will have.
426    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    /// Copies a `Font` property blob into the payload without its count, moving every
440    /// name it points at to where it now sits, and answers where the records start and how
441    /// many there are.
442    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        // Everything in the blob moved from `offset` to `at - 4 + offset`.
450        let moved = (at as u32).wrapping_sub(4);
451        self.move_font_records(at, count, moved);
452        (at as u32, count)
453    }
454
455    /// Copies a `SpanFonts` table without its count, moving every offset inside it, and
456    /// answers where the entries start and how many there are.
457    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    /// Copies a side table of fixed-size entries without its count. Each entry points at a
469    /// list of font records elsewhere in the table: the entry's list offset is moved to
470    /// where the list now sits, the names the list points at move with it, and every moved
471    /// field is marked to be counted from the start of the buffer. Answers where the
472    /// entries start and how many there are.
473    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    /// Moves the name offset of every family named by name among the `count` records at
497    /// `at`, and marks it to be counted from the start of the buffer.
498    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    /// The buffer these requests are sent as: the records, then the payload, every offset
515    /// counted from its start.
516    pub fn encoded(&mut self) -> &[u8] {
517        self.finish().0
518    }
519
520    /// Lays the records and the payload out as one buffer, every offset counted from its
521    /// start, and answers with it and the number of records.
522    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
552/// The right to ask, for the length of one Host call.
553///
554/// Not `Send`: the Renderer answers on its UI thread only, and the Host call this was
555/// handed out in is on that thread.
556pub struct Measurer {
557    _on_this_thread: PhantomData<*const ()>,
558}
559
560/// Why nothing was measured.
561#[derive(Clone, Debug, Eq, PartialEq)]
562pub enum MeasureError {
563    /// Not inside a Host call, so the Renderer is not waiting on this thread to answer.
564    OutsideHostCall,
565    /// The Renderer refused the whole call. The answers were left at zero size, and the
566    /// layout carries on with them.
567    Refused(ProtocolError),
568}
569
570/// A measurer, if the Host is inside one of its own calls on this thread, which is the
571/// only time there is a Renderer standing still to answer.
572pub 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    /// Asks every question in `requests` in one call and puts the answers in `results`, in
584    /// the order they were asked.
585    ///
586    /// A request the Renderer could not answer says so in its own status, and the rest are
587    /// answered. A call the Renderer refused as a whole leaves every answer at zero size
588    /// and comes back as an error, which the layout is free to carry on past: nothing
589    /// stops, and the next frame asks again.
590    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    /// The same, for a request buffer encoded by hand. `results` must have one slot per
602    /// record.
603    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
630/// Says once per kind of refusal what went wrong, on standard error. A refusal is the
631/// Renderer's or the Host's bug rather than the application's, and a layout that silently
632/// had every size at zero is the hardest kind of wrong to find.
633fn 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
670// ---------------------------------------------------------------------------------------
671// The mock renderer's answers.
672//
673// A test of the Host has no Renderer to ask, and wants answers it can predict. These are
674// the same rules the stand-in renderer's C answers by: every character is half the font
675// size wide, a line is one and a quarter font sizes tall, the first baseline sits one font
676// size down, and lines break at spaces. No node is known, because nothing applies a tree.
677// ---------------------------------------------------------------------------------------
678
679/// The font size the mock takes for text that does not give one.
680pub const FAKE_FONT_SIZE: f32 = 14.0;
681
682/// Answers a measure call the way the mock renderer does. Refuses a call from a thread
683/// that is not inside a Host call, which is what "off the UI thread" means to a Renderer
684/// that has no UI thread of its own.
685///
686/// # Safety
687/// `requests` must address `length` readable bytes, and `results` writable room for
688/// `count` results.
689#[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    // SAFETY: the caller promises `length` readable bytes.
703    let buffer = unsafe { std::slice::from_raw_parts(requests, length as usize) };
704    // SAFETY: the caller promises room for `count` results.
705    let results = unsafe { std::slice::from_raw_parts_mut(results, count as usize) };
706    fake_measure_slices(buffer, results)
707}
708
709/// The mock's rules over slices, for whoever already has them.
710#[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
790/// The widths of the lines `text` breaks into at `width`, breaking at spaces and at
791/// newlines. A width of zero puts every word on its own line.
792fn 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    /// One entry (index, list offset, list count) followed by one system font record
820    /// and its name, laid out as the table is on the wire.
821    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}