텍스트
텍스트는 그리는 곳에서 잽니다
텍스트는 두 쪽이 반드시 같은 답을 내야 하는 유일한 것입니다. Host 가 줄이 어디서 끊길지 정하고, 렌더러는 받은 폭 안에서 같은 글꼴로 그립니다. 두 엔진이 다르게 재면 글자가 겹치거나 틈이 생기므로, 측정기는 바꿔 끼울 수 있게 되어 있습니다.
텍스트 조각
그리기 목록의 모든 텍스트는 TextRun 입니다. 그려질 글자, 사각형, 그 글자를 가진
요소, 차지한 줄 수를 담습니다. 스타일이 섞인 문단은 스타일마다 조각이 하나씩 원문 순서대로
있습니다. samples/article 에서 문단은 열 폭에서 줄바꿈되고, 코드 블록은
pre 가 쓴 그대로의 줄을 지키므로 줄바꿈되지 않습니다.
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" 입니다.
.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 를 씁니다. 트레이트의 메서드는 하나입니다.
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 배 높이입니다. 그래서 테스트의 기대 사각형이 정확한 수치입니다.
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 가 잽니다.
- 보통 글자에
strong이나em이 섞인 문단처럼 글꼴이 섞인 문단 - 표 칸 안의 텍스트
- 입력 컨트롤과
img같은 대체 요소
모든 조각을 그리는 엔진이 재는 것이 목표이므로 이 부분은 부분 입니다.
Compose 로 재기
Parley 의 크기는 Chromium 과 충분히 맞지 않습니다. VS Code 워크벤치에서 줄바꿈 22 곳 중 6 곳이 다른 자리에서 끊겼고, 한 줄의 폭이 최대 14.3% 달랐고, 글자 위치가 최대 39.89px 어긋났습니다. 주된 이유는 Parley 가 Apple 시스템 글꼴의 광학 크기를 고르지 않는 것입니다. 텍스트 크기를 VS Code 가 잰 값으로 넣으면 상자는 1px 이내로 맞았습니다.
그래서 Host 는 레이아웃 도중 동기 호출 한 번으로 렌더러에 텍스트 조각의 크기를 묻고, Compose 는
자신이 그릴 글꼴로 답하게 됩니다. 그 호출의 렌더러 쪽은 compose-rust 에서 만들고 있습니다
(부분,
#43). 그 호출이 끼워지는
자리가 TextMeasurer 트레이트이므로, 측정기를 정하지 않은 앱은 아무것도 바꾸지 않고
Compose 의 답을 받게 됩니다.
아직 없는 것
- 시스템 글자 크기와 확대. 계획 HTML 화면은
VS Code 의
window.zoomLevel처럼 화면 전체가 확대되어, 행과 아이콘과 글자가 같이 커지고 잘리는 것이 없게 됩니다. 확대의 시작값은 시스템 글자 크기를 따릅니다. - 마크다운. 계획 마크다운을 Compose 위젯으로
된 보통의
rsx!트리로 바꾸는 별도 크레이트dioxus-compose-markdown이며, 스트리밍도 포함합니다 (#24).