dioxus-compose 가이드

텍스트

텍스트는 그리는 곳에서 잽니다

텍스트는 두 쪽이 반드시 같은 답을 내야 하는 유일한 것입니다. Host 가 줄이 어디서 끊길지 정하고, 렌더러는 받은 폭 안에서 같은 글꼴로 그립니다. 두 엔진이 다르게 재면 글자가 겹치거나 틈이 생기므로, 측정기는 바꿔 끼울 수 있게 되어 있습니다.

텍스트 조각

그리기 목록의 모든 텍스트는 TextRun 입니다. 그려질 글자, 사각형, 그 글자를 가진 요소, 차지한 줄 수를 담습니다. 스타일이 섞인 문단은 스타일마다 조각이 하나씩 원문 순서대로 있습니다. samples/article 에서 문단은 열 폭에서 줄바꿈되고, 코드 블록은 pre 가 쓴 그대로의 줄을 지키므로 줄바꿈되지 않습니다.

samples/article/src/lib.rs, 줄임
p { id: "first",
    "A Dioxus app written with HTML tags and CSS does not need a browser to be laid "
    "out. The Host runs the same style and layout engines a browser would, and hands "
    "the renderer nothing but rectangles, colours and runs of text."
}
pre { id: "sample", code { "{SAMPLE}" } }

공백과 변환

측정기에는 그려질 모습 그대로의 텍스트가 갑니다. 공백은 white-space (normal, nowrap, pre, pre-wrap, pre-line, break-spaces)에 따라 이미 합쳐지거나 보존되어 있고, text-transform 도 이미 적용되어 있습니다. article 의 머리 문구는 text-transform: uppercase 아래에 "Field notes" 로 쓰여 있고, 측정기가 받는 것은 언제나 "FIELD NOTES" 입니다.

samples/workbench/src/lib.rs
.tree-row {
    display: flex;
    align-items: center;
    height: 22px;
    white-space: nowrap;
}

letter-spacing, line-height, font-family, font-size, font-weight, font-style 는 모두 TextStyle 에 담겨 측정기로 갑니다. 생성된 내용(::before, ::after)도 다른 텍스트와 똑같이 잽니다.

측정기

HtmlConfig::measurer 는 TextMeasurer 를 구현한 것이면 무엇이든 받습니다. 넘기지 않으면 blitz-dom 에 딸린 텍스트 엔진 Parley 로 재는 ParleyMeasurer 를 씁니다. 트레이트의 메서드는 하나입니다.

dioxus_compose::html
pub trait TextMeasurer {
    fn measure(&mut self, request: &TextMeasureRequest<'_>) -> TextMetrics;
}

// The request: text, style, width and white space.
// WidthConstraint is MaxContent, MinContent or AtMost(width).
// The answer: width, height, first_baseline and line_count, in CSS pixels.

flex 항목은 최종 폭을 받기 전에 가장 좁은 크기와 가장 넓은 크기를 먼저 질문받으므로, 레이아웃은 같은 텍스트를 다른 폭으로 여러 번 물을 수 있습니다. 그래서 답은 요청에만 달려 있어야 합니다.

예제의 테스트는 종이에서 계산할 수 있는 측정기를 씁니다. 모든 글자는 글꼴 크기의 절반 폭이고, 한 줄은 글꼴 크기의 1.25 배 높이입니다. 그래서 테스트의 기대 사각형이 정확한 수치입니다.

samples/support/src/lib.rs, 줄임
impl TextMeasurer for Measurer {
    fn measure(&mut self, request: &TextMeasureRequest<'_>) -> TextMetrics {
        let advance = request.style.font_size * 0.5 + request.style.letter_spacing;
        let line = line_height(request.style);
        let max_chars = match request.width {
            WidthConstraint::MaxContent => usize::MAX,
            WidthConstraint::MinContent => 0,
            WidthConstraint::AtMost(width) => (width / advance + 1e-3).floor() as usize,
        };
        let lines = break_lines(request.text, max_chars);
        let width = lines
            .iter()
            .map(|line| line.chars().count() as f32 * advance)
            .fold(0.0, f32::max);
        TextMetrics {
            width,
            height: lines.len() as f32 * line,
            first_baseline: line * 0.75,
            line_count: lines.len() as u32,
        }
    }
}

측정기를 거치는 텍스트

측정기는 글꼴이 하나뿐이고, 안에 인라인 상자가 없고, 글자에 여백이나 테두리를 주는 인라인 요소가 없는 텍스트를 잽니다. 대부분의 라벨, 제목, 문단과 a 같은 단순한 인라인 요소 안의 텍스트가 여기에 들어갑니다. 나머지는 blitz-dom 자체의 레이아웃으로 돌아가 Parley 가 잽니다.

모든 조각을 그리는 엔진이 재는 것이 목표이므로 이 부분은 부분 입니다.

Compose 로 재기

Parley 의 크기는 Chromium 과 충분히 맞지 않습니다. VS Code 워크벤치에서 줄바꿈 22 곳 중 6 곳이 다른 자리에서 끊겼고, 한 줄의 폭이 최대 14.3% 달랐고, 글자 위치가 최대 39.89px 어긋났습니다. 주된 이유는 Parley 가 Apple 시스템 글꼴의 광학 크기를 고르지 않는 것입니다. 텍스트 크기를 VS Code 가 잰 값으로 넣으면 상자는 1px 이내로 맞았습니다.

그래서 Host 는 레이아웃 도중 동기 호출 한 번으로 렌더러에 텍스트 조각의 크기를 묻고, Compose 는 자신이 그릴 글꼴로 답하게 됩니다. 그 호출의 렌더러 쪽은 compose-rust 에서 만들고 있습니다 (부분, #43). 그 호출이 끼워지는 자리가 TextMeasurer 트레이트이므로, 측정기를 정하지 않은 앱은 아무것도 바꾸지 않고 Compose 의 답을 받게 됩니다.

아직 없는 것