Compare commits

...
4 changed files with 531 additions and 19 deletions
@@ -26,7 +26,11 @@ use iced::advanced::{
text::{Alignment, LineHeight, Shaping},
};
use iced::{Color, Font, Pixels, Point, Rectangle, Size, widget::canvas::Text};
use std::{collections::VecDeque, ops::Range, sync::Arc};
use std::{
collections::{HashSet, VecDeque},
ops::Range,
sync::Arc,
};
use unicode_segmentation::UnicodeSegmentation;
const MAX_SOURCE_BYTES: usize = 64 * 1024;
@@ -102,6 +106,7 @@ pub(super) struct Label {
clusters: Vec<FieldCluster>,
source_spans: Vec<SourceSpan>,
retained_normalized: usize,
field_index: FieldIndex,
}
#[derive(Clone, Debug, PartialEq)]
@@ -142,6 +147,69 @@ struct FieldCluster {
missing: bool,
}
/// Immutable overlap membership, built from Normalized's ordered, disjoint
/// scalar/separator spans. Clusters may overlap and need not be in visual order.
/// Construction costs O(C log S + I), storage O(S + C + I), where I is the
/// number of actual span/cluster overlaps, rather than the full S*C product.
#[derive(Clone, Debug)]
struct FieldIndex {
clusters_by_span: Vec<Vec<usize>>,
source_extents: Vec<Option<SourceExtents>>,
}
#[derive(Clone, Copy, Debug)]
struct SourceExtents {
minimum_end: usize,
maximum_start: usize,
}
impl FieldIndex {
fn new(spans: &[SourceSpan], clusters: &[FieldCluster]) -> Self {
debug_assert!(
spans
.iter()
.all(|span| span.normalized.start < span.normalized.end)
);
debug_assert!(
spans
.windows(2)
.all(|pair| pair[0].normalized.end <= pair[1].normalized.start)
);
let mut result = Self {
clusters_by_span: vec![vec![]; spans.len()],
source_extents: vec![None; clusters.len()],
};
for (index, cluster) in clusters.iter().enumerate() {
if cluster.normalized.start >= cluster.normalized.end {
continue;
}
let first =
spans.partition_point(|span| span.normalized.end <= cluster.normalized.start);
for (offset, span) in spans[first..].iter().enumerate() {
if span.normalized.start >= cluster.normalized.end {
break;
}
result.clusters_by_span[first + offset].push(index);
let extents = result.source_extents[index].get_or_insert(SourceExtents {
minimum_end: span.source.end,
maximum_start: span.source.start,
});
extents.minimum_end = extents.minimum_end.min(span.source.end);
extents.maximum_start = extents.maximum_start.max(span.source.start);
}
}
result
}
}
// Translation rejects nonfinite rectangle components before deduplication.
// For finite f32 values, bit equality differs from `==` only at signed zero.
// Normalize only the key: preserve the first rectangle and its original bits.
fn rectangle_key(bounds: Rectangle) -> [u32; 4] {
[bounds.x, bounds.y, bounds.width, bounds.height]
.map(|value| if value == 0. { 0 } else { value.to_bits() })
}
impl Label {
/// Geometry and coverage are separate: truncation is not missing-font
/// coverage, and a positive .notdef advance is not represented field text.
@@ -168,7 +236,7 @@ impl Label {
shared_cluster: false,
};
let mut selected = vec![false; self.clusters.len()];
for span in &self.source_spans {
for (span_index, span) in self.source_spans.iter().enumerate() {
if span.whitespace || !overlaps(&span.source, &field) {
continue;
}
@@ -178,27 +246,25 @@ impl Label {
append_range(&mut result.truncated, span.source.clone());
continue;
}
let matching: Vec<_> = self
.clusters
.iter()
.enumerate()
.filter(|(_, cluster)| overlaps(&cluster.normalized, &span.normalized))
.collect();
result.shared_cluster |= matching.iter().any(|(_, cluster)| {
let matching = &self.field_index.clusters_by_span[span_index];
result.shared_cluster |= matching.iter().any(|&index| {
let cluster = &self.clusters[index];
cluster.normalized.end > self.retained_normalized
|| self.source_spans.iter().any(|other| {
overlaps(&other.normalized, &cluster.normalized)
&& !overlaps(&other.source, &field)
|| self.field_index.source_extents[index].is_some_and(|extents| {
// Any overlapping source span outside the requested
// field is exactly either a too-small end or too-large
// start. Include whitespace/nonpainting spans, as before.
extents.minimum_end <= field.start || extents.maximum_start >= field.end
})
});
let issue = if span.non_painting {
Some(FieldIssue::NonPaintingScalar)
} else if matching.iter().any(|(_, cluster)| cluster.missing) {
} else if matching.iter().any(|&index| self.clusters[index].missing) {
Some(FieldIssue::MissingGlyph)
} else if matching.is_empty()
|| matching
.iter()
.all(|(_, cluster)| cluster.rectangles.is_empty())
.all(|&index| self.clusters[index].rectangles.is_empty())
{
Some(FieldIssue::NoAdvanceGeometry)
} else {
@@ -212,10 +278,11 @@ impl Label {
continue;
}
append_range(&mut result.covered, span.source.clone());
for (index, _) in matching {
for &index in matching {
selected[index] = true;
}
}
let mut seen = HashSet::new();
for (cluster, selected) in self.clusters.iter().zip(selected) {
if !selected {
continue;
@@ -225,7 +292,7 @@ impl Label {
Point::new(position.x + bounds.x, position.y + bounds.y),
bounds.size(),
)?;
if !result.rectangles.contains(&translated) {
if seen.insert(rectangle_key(translated)) {
result.rectangles.push(translated);
}
}
@@ -502,6 +569,7 @@ impl Cache {
);
field_clusters(entry, zoom)?
};
let field_index = FieldIndex::new(&normalized.spans, &clusters);
let label = Arc::new(Label {
source: source.into(),
displayed,
@@ -513,6 +581,7 @@ impl Cache {
clusters,
source_spans: normalized.spans,
retained_normalized: retained,
field_index,
});
if self.entries.len() == MAX_ENTRIES {
self.entries.pop_front();
@@ -684,3 +753,7 @@ mod tests;
#[cfg(test)]
#[path = "scenario_label/field_tests.rs"]
mod field_tests;
#[cfg(test)]
#[path = "scenario_label/lookup_tests.rs"]
mod lookup_tests;
@@ -31,9 +31,12 @@ fn assert_ligature_fixture_face(oracle: &Paragraph) {
let mut count = 0;
for glyph in oracle.buffer().layout_runs().flat_map(|run| run.glyphs) {
assert_eq!(
fonts.raw().db().with_face_data(glyph.font_id, |bytes, index| {
index == 0 && bytes == LIGATURE_FONT
}),
fonts
.raw()
.db()
.with_face_data(glyph.font_id, |bytes, index| {
index == 0 && bytes == LIGATURE_FONT
}),
Some(true),
"the actual shaped face must be the byte-pinned fixture"
);
@@ -568,6 +571,13 @@ fn field_lookup_4096_scalars_and_combining_heavy_full_coverage() {
.unwrap();
let resolve_ns = start.elapsed().as_nanos();
assert_eq!(label.displayed, source);
// The preserved 4c9 lookup runs on the exact same immutable shaped
// label. Its timing is separate from the optimized samples below.
let start = std::time::Instant::now();
let original = label
.original_field_layout(&session, 1., Point::ORIGIN, 0..source.len())
.unwrap();
let original_lookup_ns = start.elapsed().as_nanos();
let mut lookup_ns = vec![];
for _ in 0..3 {
let start = std::time::Instant::now();
@@ -579,11 +589,33 @@ fn field_lookup_4096_scalars_and_combining_heavy_full_coverage() {
assert!(field.truncated.is_empty() && field.unresolved.is_empty());
assert!(!field.partially_retained && !field.shared_cluster);
assert_complete_boxes(&field.rectangles, expected.clone());
super::lookup_tests::assert_exact(&field, &original);
}
// Fresh label construction plus lookup, not just a retained-index
// measurement: moving the old quadratic work into resolve cannot hide.
let mut fresh_resolve_lookup_ns = vec![];
for _ in 0..3 {
let mut fresh_cache = Cache::default();
let start = std::time::Instant::now();
let fresh = fresh_cache
.resolve(&session, &source, 1_000_000., 1., style)
.unwrap();
let field = fresh
.field_layout(&session, 1., Point::ORIGIN, 0..source.len())
.unwrap();
fresh_resolve_lookup_ns.push(start.elapsed().as_nanos());
super::lookup_tests::assert_exact(&field, &original);
assert_complete_boxes(&field.rectangles, expected.clone());
}
records.push(serde_json::json!({
"case": name, "scalars": 4096, "graphemes": expected_graphemes,
"source_bytes": source.len(), "expected_rectangles": expected.len(),
"resolved_fonts": identities, "resolve_ns": resolve_ns, "lookup_ns": lookup_ns,
"original_lookup_ns": original_lookup_ns,
"fresh_resolve_lookup_ns": fresh_resolve_lookup_ns,
"source_spans": label.source_spans.len(), "clusters": label.clusters.len(),
"overlap_memberships": label.field_index.clusters_by_span.iter().map(Vec::len).sum::<usize>(),
"exact_original_result_including_rectangle_bits": "passed",
"full_coverage_and_exact_geometry": "passed",
}));
}
@@ -0,0 +1,381 @@
//! Exact differential oracle from 4c9bc87e4e66bba573dd63b23df5ea039f85d0ca.
//! The old method body is retained verbatim; only its name/visibility changes.
//! Its unchanged validation/translation helpers remain in the parent module.
use super::*;
pub(super) fn assert_exact(actual: &FieldLayout, expected: &FieldLayout) {
assert_eq!(actual, expected);
// PartialEq deliberately equates signed zero; the observable first box
// must nevertheless retain exactly the old representation and order.
for (actual, expected) in actual.rectangles.iter().zip(&expected.rectangles) {
let bits = |r: &Rectangle| [r.x, r.y, r.width, r.height].map(f32::to_bits);
assert_eq!(bits(actual), bits(expected));
}
}
fn compare(
label: &Label,
session: &RenderSession,
zoom: f32,
position: Point,
field: Range<usize>,
) {
let old = label.original_field_layout(session, zoom, position, field.clone());
let new = label.field_layout(session, zoom, position, field);
match (new, old) {
(Ok(new), Ok(old)) => assert_exact(&new, &old),
(new, old) => assert_eq!(new, old),
}
}
fn all_fields(label: &Label, session: &RenderSession, position: Point) {
let boundaries: Vec<_> = label
.source
.char_indices()
.map(|(i, _)| i)
.chain(std::iter::once(label.source.len()))
.collect();
for &start in &boundaries {
for &end in &boundaries {
compare(label, session, label.measured_zoom, position, start..end);
}
}
}
fn synthetic(source: &str, retained: usize, clusters: Vec<FieldCluster>) -> (RenderSession, Label) {
let session = RenderSession::after_fonts_settled_and_renderer_reset().unwrap();
let normalized = Normalized::new(source);
let field_index = FieldIndex::new(&normalized.spans, &clusters);
let label = Label {
source: source.into(),
displayed: normalized.text,
retained_source: 0..source.len(),
bounds: Rectangle::with_size(Size::ZERO),
style: Style::default(),
measured_session: session.clone(),
measured_zoom: 1.,
clusters,
source_spans: normalized.spans,
retained_normalized: retained,
field_index,
};
(session, label)
}
fn cluster(range: Range<usize>, rectangles: Vec<Rectangle>, missing: bool) -> FieldCluster {
FieldCluster {
normalized: range,
rectangles,
missing,
}
}
#[test]
fn indexed_lookup_matches_original_for_real_shaping_and_every_field_boundary() {
let session = RenderSession::after_fonts_settled_and_renderer_reset().unwrap();
let sources = [
"",
" \t\n ",
"ffi office",
" \talpha \r\n beta ",
"a\u{301}\u{fe0f} b\u{2060}c",
"אבג abc דהו",
"A😀Z",
];
let mut nonempty = 0;
for source in sources {
for shaping in [Shaping::Auto, Shaping::Advanced] {
for zoom in [0.5, 1.75] {
let mut cache = Cache::default();
for width in [0., 25., 500.] {
let label = cache
.resolve(
&session,
source,
width,
zoom,
Style {
shaping,
..Style::default()
},
)
.unwrap();
nonempty += usize::from(!label.clusters.is_empty());
all_fields(&label, &session, Point::ORIGIN);
all_fields(&label, &session, Point::new(-37.25, 16.125));
}
}
}
}
assert!(
nonempty >= sources.len(),
"must compare actual shaped geometry as well as empty labels"
);
}
#[test]
fn indexed_lookup_matches_original_for_overlapping_and_reordered_clusters() {
// Normalization is "a + acute + VS16 + space + b + space + c".
// Cluster intervals overlap, nest, span separators, and occur in a
// deliberately non-source/non-visual order. This tests the join rather
// than assuming the simpler cluster partition of most actual fonts.
let source = " a\u{301}\u{fe0f} b c ";
assert_eq!(Normalized::new(source).text.len(), 10);
let a = Rectangle::new(Point::new(30., 3.), Size::new(4., 12.));
let b = Rectangle::new(Point::new(-20., -1.), Size::new(7., 11.));
let c = Rectangle::new(Point::new(9., 4.), Size::new(2., 10.));
let cases = vec![
cluster(7..10, vec![b, a], false),
cluster(0..6, vec![a, c, a], false),
cluster(1..3, vec![], false),
cluster(3..8, vec![c], true),
cluster(0..10, vec![b], false),
cluster(6..7, vec![c], false),
cluster(9..13, vec![a], false), // can extend into a synthetic ellipsis
cluster(3..3, vec![c], true), // empty interval cannot match anything
cluster(8..7, vec![b], true), // nor can a reversed interval
];
for reverse in [false, true] {
let mut clusters = cases.clone();
if reverse {
clusters.reverse();
}
for retained in [0, 1, 3, 6, 7, 8, 10] {
let (session, label) = synthetic(source, retained, clusters.clone());
all_fields(&label, &session, Point::new(-0., -0.));
all_fields(&label, &session, Point::new(0.125, -64.));
}
}
let (session, priority) = synthetic(
"a\u{2060}b",
5,
vec![
cluster(0..1, vec![], false),
cluster(1..4, vec![a], true),
cluster(4..5, vec![b], true),
],
);
all_fields(&priority, &session, Point::ORIGIN);
let result = priority
.field_layout(&session, 1., Point::ORIGIN, 0..5)
.unwrap();
assert_eq!(
result.unresolved,
vec![
UnresolvedField {
source: 0..1,
reason: FieldIssue::NoAdvanceGeometry
},
UnresolvedField {
source: 1..4,
reason: FieldIssue::NonPaintingScalar
},
UnresolvedField {
source: 4..5,
reason: FieldIssue::MissingGlyph
},
]
);
assert!(result.covered.is_empty() && result.rectangles.is_empty());
}
#[test]
fn indexed_lookup_preserves_first_rectangle_bits_and_shared_status() {
let negative = Rectangle::new(Point::new(-0., -0.), Size::new(6., 12.));
let positive = Rectangle::new(Point::ORIGIN, Size::new(6., 12.));
assert_eq!(negative, positive, "Rectangle equality merges signed zero");
let other = Rectangle::new(Point::new(-20., 4.), Size::new(3., 12.));
let (session, label) = synthetic(
"a b",
3,
vec![
cluster(2..3, vec![negative, other], false),
cluster(0..1, vec![positive, negative], false),
],
);
let result = label
.field_layout(&session, 1., Point::new(-0., -0.), 0..3)
.unwrap();
assert_eq!(result.covered, vec![0..1, 2..3]);
assert!(!result.shared_cluster);
assert_eq!(result.rectangles.len(), 2);
assert_eq!(result.rectangles[0].x.to_bits(), (-0f32).to_bits());
assert_eq!(result.rectangles[0].y.to_bits(), (-0f32).to_bits());
assert_eq!(result.rectangles[1], other);
assert_exact(
&result,
&label
.original_field_layout(&session, 1., Point::new(-0., -0.), 0..3)
.unwrap(),
);
let (session, shared) = synthetic("a b", 3, vec![cluster(0..3, vec![other], false)]);
let partial = shared
.field_layout(&session, 1., Point::ORIGIN, 0..1)
.unwrap();
assert_eq!(partial.covered, vec![0..1]);
assert!(partial.shared_cluster);
let complete = shared
.field_layout(&session, 1., Point::ORIGIN, 0..3)
.unwrap();
assert!(!complete.shared_cluster);
all_fields(&shared, &session, Point::ORIGIN);
// The same logical cluster crosses the retained prefix: its box remains
// shared while the source scalar after that prefix remains truncated.
let (session, clipped) = synthetic("abc", 2, vec![cluster(0..3, vec![other], false)]);
let clipped = clipped
.field_layout(&session, 1., Point::ORIGIN, 0..3)
.unwrap();
assert_eq!(clipped.covered, vec![0..2]);
assert_eq!(clipped.truncated, vec![2..3]);
assert!(clipped.shared_cluster && clipped.partially_retained);
}
#[test]
fn indexed_lookup_preserves_error_precedence_and_overflow_rejection() {
let (session, label) = synthetic(
"éa",
3,
vec![cluster(
0..3,
vec![Rectangle::new(
Point::new(f32::MAX, 0.),
Size::new(f32::MAX, 1.),
)],
false,
)],
);
let stale = RenderSession::after_fonts_settled_and_renderer_reset().unwrap();
assert_eq!(
label.field_layout(&stale, f32::NAN, Point::new(f32::NAN, 0.), 7..8),
Err(Error::StaleRenderSession)
);
assert_eq!(
label.field_layout(&session, 2., Point::new(f32::NAN, 0.), 7..8),
Err(Error::StaleZoom)
);
assert_eq!(
label.field_layout(&session, 1., Point::new(f32::NAN, 0.), 7..8),
Err(Error::InvalidPosition)
);
assert_eq!(
label.field_layout(&session, 1., Point::ORIGIN, 1..2),
Err(Error::InvalidFieldRange)
);
assert_eq!(
label.field_layout(&session, 1., Point::ORIGIN, 0..3),
Err(Error::InvalidPosition)
);
// An unselected overflowing box must not turn a valid empty query into an error.
assert!(
label
.field_layout(&session, 1., Point::ORIGIN, 0..0)
.unwrap()
.rectangles
.is_empty()
);
for guard in [&session, &stale] {
for zoom in [1., 2., f32::NAN] {
for position in [
Point::ORIGIN,
Point::new(f32::NAN, 0.),
Point::new(f32::MAX, 0.),
] {
for range in [0..0, 0..3, 1..2, 3..2, 0..4] {
compare(&label, guard, zoom, position, range);
}
}
}
}
}
impl Label {
pub(super) fn original_field_layout(
&self,
session: &RenderSession,
zoom: f32,
position: Point,
field: Range<usize>,
) -> Result<FieldLayout, Error> {
self.check(session, zoom)?;
translated_bounds(position, Size::ZERO)?;
if field.start > field.end || self.source.get(field.clone()).is_none() {
return Err(Error::InvalidFieldRange);
}
let mut result = FieldLayout {
rectangles: vec![],
covered: vec![],
truncated: vec![],
unresolved: vec![],
partially_retained: false,
shared_cluster: false,
};
let mut selected = vec![false; self.clusters.len()];
for span in &self.source_spans {
if span.whitespace || !overlaps(&span.source, &field) {
continue;
}
// Source spans for text are exact UTF-8 scalars, not intervals
// between ends (which could assign trimmed whitespace to text).
if span.normalized.end > self.retained_normalized {
append_range(&mut result.truncated, span.source.clone());
continue;
}
let matching: Vec<_> = self
.clusters
.iter()
.enumerate()
.filter(|(_, cluster)| overlaps(&cluster.normalized, &span.normalized))
.collect();
result.shared_cluster |= matching.iter().any(|(_, cluster)| {
cluster.normalized.end > self.retained_normalized
|| self.source_spans.iter().any(|other| {
overlaps(&other.normalized, &cluster.normalized)
&& !overlaps(&other.source, &field)
})
});
let issue = if span.non_painting {
Some(FieldIssue::NonPaintingScalar)
} else if matching.iter().any(|(_, cluster)| cluster.missing) {
Some(FieldIssue::MissingGlyph)
} else if matching.is_empty()
|| matching
.iter()
.all(|(_, cluster)| cluster.rectangles.is_empty())
{
Some(FieldIssue::NoAdvanceGeometry)
} else {
None
};
if let Some(reason) = issue {
result.unresolved.push(UnresolvedField {
source: span.source.clone(),
reason,
});
continue;
}
append_range(&mut result.covered, span.source.clone());
for (index, _) in matching {
selected[index] = true;
}
}
for (cluster, selected) in self.clusters.iter().zip(selected) {
if !selected {
continue;
}
for bounds in &cluster.rectangles {
let translated = translated_bounds(
Point::new(position.x + bounds.x, position.y + bounds.y),
bounds.size(),
)?;
if !result.rectangles.contains(&translated) {
result.rectangles.push(translated);
}
}
}
result.partially_retained = !result.truncated.is_empty()
&& (!result.covered.is_empty() || !result.unresolved.is_empty());
Ok(result)
}
}
@@ -0,0 +1,26 @@
# Indexed field lookup experiment
This unwired experiment extends source `4c9bc87e4e66bba573dd63b23df5ea039f85d0ca`. It does not adopt measured labels or field navigation in the renderer. This revision has **not been compiled or executed**. The baseline debug diagnostic reported roughly 2.4–6.6 seconds for a 4096-scalar plain field and 1.3–1.9 seconds for a 4096-scalar combining-heavy field; these are motivation, not results for this patch.
`Label` now stores an overlap index built from the winning shaped buffer's clusters and the existing ordered, disjoint normalized scalar spans. For S spans, C clusters, and I actual span/cluster overlap memberships, index construction takes O(C log S + I) work and O(S + C + I) storage. A lookup visits the source spans and their indexed matches, then emits selected cluster rectangles in the original cluster/rectangle order. Hash membership replaces repeated linear rectangle searches. Signed zeros share a deduplication key, as with `Rectangle::PartialEq`, while the first original rectangle's bits remain unchanged. Hash iteration never determines output order.
Per-cluster source end/start extrema preserve the old shared-cluster predicate, including whitespace and nonpainting spans. Source scalar overlap, truncation, missing-glyph and nonpainting precedence, partial/shared flags, invalid-range errors, session/font/zoom guards, and position/overflow rejection are unchanged. No new length limit is introduced. Index storage can be dense for intentionally overlapping synthetic clusters. Existing shaping and `field_clusters` extraction are unchanged, including that extractor's per-cluster linear rectangle deduplication; this is not a claim of globally linear shaping or preparation.
The test-only `Label::original_field_layout` retains the baseline method body verbatim. Four new normal tests compare every `FieldLayout` field and every rectangle's exact component bits and position in the output:
- Real Auto/Advanced shaping at two zooms and three widths, over every legal UTF-8 boundary pair, including scalar boundaries inside combining graphemes.
- Overlapping, reordered, whitespace-spanning, missing, empty and reversed cluster ranges; partial retention and nonpainting/coverage precedence.
- First-occurrence signed-zero bits, shared clusters and truncation.
- Guard/error precedence, malformed fields, nonfinite positions and overflow.
The existing ignored `field_lookup_4096_scalars_and_combining_heavy_full_coverage` diagnostic retains its two inputs and independent Paragraph coverage/rectangle oracle. It now times the original lookup separately, three indexed queries on the retained label, and three fresh-cache resolve-plus-lookup operations. Exact old/new results and independent complete rectangle sets are checked outside timed intervals. The original lookup precedes indexed samples; font caches are warm. These are debug experiment measurements, not a latency guarantee or a cold-process benchmark. Fresh resolution includes index construction so its cost cannot disappear into an unmeasured preprocessing step.
The isolated harness should list 29 tests: 25 normal and four ignored. The production desktop does not register this module. Reuse the explicit dependency-bound harness recipe for the baseline, replacing its source binding with this revision and adding `scenario_label/lookup_tests.rs` to the input closure. Required execution is the normal suite plus these three ignored tests, each in a fresh process:
```text
scenario_label::field_tests::advanced_ligature_partial_field_uses_whole_shared_cluster_not_fraction
scenario_label::field_tests::auto_ascii_to_ellipsis_advanced_transition_uses_final_buffer
scenario_label::tests::actual_font_load_rejects_retained_label_and_old_session
```
Run the timing diagnostic separately with `--exact scenario_label::field_tests::field_lookup_4096_scalars_and_combining_heavy_full_coverage --ignored --nocapture --test-threads=1`. It remains optional functional-gate evidence; its exact full-coverage assertions are not waived. The new differential filter is `scenario_label::lookup_tests::` (four tests). The pinned-font checks retain their existing isolated-process contract. Other real-shaping differential cases use the current system default font and compare both methods on the same resolved buffer; they do not establish cross-machine glyph identity.