Skip to content

Commit bb486e5

Browse files
[wip] map of logical to graphical glyph runs order
1 parent 715be29 commit bb486e5

File tree

3 files changed

+142
-50
lines changed

3 files changed

+142
-50
lines changed

sketches/text-layout/main.c

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -329,21 +329,27 @@ int main()
329329
}
330330

331331
{
332-
oc_move_to(400, 500);
332+
oc_move_to(300, 450);
333333

334-
oc_text_line* line = oc_text_line_from_utf8(scratch.arena,
335-
OC_STR8("Hello مرحبا שלום Bonjour"),
336-
&(oc_text_attributes){
337-
.font = arabicFont,
338-
.fontSize = fontSize,
339-
.color = { 0, 0, 0, 1 },
340-
});
334+
oc_str32 codepoints = oc_utf8_push_to_codepoints(scratch.arena, OC_STR8("Hello مرحبا שלום Bonjour"));
335+
oc_text_line* line = oc_text_line_from_utf32(scratch.arena,
336+
codepoints,
337+
&(oc_text_attributes){
338+
.font = arabicFont,
339+
.fontSize = fontSize,
340+
.color = { 0, 0, 0, 1 },
341+
});
341342

342343
oc_text_line_draw(line);
344+
345+
oc_rect box = oc_text_line_get_metrics_for_range(line, 0, codepoints.len).logical;
346+
oc_set_width(1);
347+
oc_set_color_rgba(1, 0, 0, 1);
348+
// oc_rectangle_stroke(300 + box.x, 450 + box.y, box.w, box.h);
343349
}
344350

345351
{
346-
oc_move_to(400, 550);
352+
oc_move_to(300, 550);
347353

348354
oc_text_line* line = oc_text_line_from_utf8(scratch.arena,
349355
OC_STR8("Hello こんにちは Bonjour"),
@@ -358,7 +364,7 @@ int main()
358364
oc_text_metrics metrics = oc_text_line_get_metrics_for_range(line, rangeStart, rangeEnd);
359365
oc_set_width(1);
360366
oc_set_color_rgba(1, 0, 0, 1);
361-
oc_rectangle_stroke(400 + metrics.logical.x,
367+
oc_rectangle_stroke(300 + metrics.logical.x,
362368
550 + metrics.logical.y,
363369
metrics.logical.w,
364370
metrics.logical.h);

src/graphics/graphics_common.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,13 +1170,15 @@ typedef struct oc_text_line
11701170
oc_glyph_run** runs; //TODO: could be just an array of glyph_runs if we change glyph run creation to take a run struct to init..
11711171
oc_text_attributes* attributes;
11721172
oc_vec2* offsets;
1173+
u64* logicalToGraphicalRuns;
11731174

11741175
oc_text_metrics metrics;
11751176
} oc_text_line;
11761177

11771178
typedef struct oc_text_item
11781179
{
11791180
oc_list_elt listElt;
1181+
u64 logicalIndex;
11801182
u64 start;
11811183
u64 end;
11821184
oc_text_direction direction;
@@ -1275,6 +1277,7 @@ oc_text_line* oc_text_line_from_utf32(oc_arena* arena, oc_str32 codepoints, oc_t
12751277
&& script != OC_UNICODE_SCRIPT_INHERITED)
12761278
{
12771279
oc_text_item* item = oc_arena_push_type(scratch.arena, oc_text_item);
1280+
item->logicalIndex = runCount;
12781281
item->start = runStart;
12791282
item->end = i;
12801283
item->direction = dirRun->direction;
@@ -1288,6 +1291,7 @@ oc_text_line* oc_text_line_from_utf32(oc_arena* arena, oc_str32 codepoints, oc_t
12881291
}
12891292
//NOTE: push last script run
12901293
oc_text_item* item = oc_arena_push_type(scratch.arena, oc_text_item);
1294+
item->logicalIndex = runCount;
12911295
item->start = runStart;
12921296
item->end = dirRun->end;
12931297
item->direction = dirRun->direction;
@@ -1309,6 +1313,16 @@ oc_text_line* oc_text_line_from_utf32(oc_arena* arena, oc_str32 codepoints, oc_t
13091313
}
13101314
}
13111315
}
1316+
//NOTE: compute map of logical to graphical runs
1317+
line->logicalToGraphicalRuns = oc_arena_push_array(arena, u64, runCount);
1318+
memset(line->logicalToGraphicalRuns, 0, runCount * sizeof(u64));
1319+
1320+
u64 graphicalIndex = 0;
1321+
oc_list_for(scriptRuns, run, oc_text_item, listElt)
1322+
{
1323+
line->logicalToGraphicalRuns[run->logicalIndex] = graphicalIndex;
1324+
graphicalIndex++;
1325+
}
13121326

13131327
//NOTE: allocate runs
13141328
line->runCount = runCount;

src/graphics/graphics_text_native.c

Lines changed: 112 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,10 @@ oc_glyph_run* oc_harfbuzz_font_shape(oc_arena* arena,
221221

222222
glyph->metrics.advance = (oc_vec2){ glyphPos[i].x_advance, -glyphPos[i].y_advance };
223223

224+
//TODO: we should compute run metrics with the general range-based function at the end
224225
run->metrics.ink = oc_rect_combine(run->metrics.ink, glyph->metrics.ink);
225-
run->metrics.logical.w = oc_max(run->metrics.logical.w, run->metrics.advance.x);
226226
run->metrics.advance = oc_vec2_add(run->metrics.advance, glyph->metrics.advance);
227+
run->metrics.logical.w = oc_max(run->metrics.logical.w, run->metrics.advance.x);
227228
}
228229

229230
//------------------------------------------------------------------------------
@@ -244,64 +245,90 @@ oc_glyph_run* oc_harfbuzz_font_shape(oc_arena* arena,
244245
{
245246
//NOTE: collate clusters and cluster widths
246247
u64* clusters = oc_arena_push_array(scratch.arena, u64, run->glyphCount);
247-
u64* clusterFirstGlyphs = oc_arena_push_array(scratch.arena, u64, run->glyphCount);
248248
oc_text_metrics* clusterMetrics = oc_arena_push_array(scratch.arena, oc_text_metrics, run->glyphCount);
249249

250250
u64 clusterCount = 0;
251251
u64 currentClusterValue = 0;
252252

253253
currentClusterValue = glyphInfo[0].cluster;
254254
clusters[clusterCount] = currentClusterValue;
255-
clusterFirstGlyphs[clusterCount] = 0;
256255
clusterMetrics[clusterCount] = run->glyphs[0].metrics;
257256
clusterCount++;
258257

259258
for(u64 glyphIndex = 1; glyphIndex < run->glyphCount; glyphIndex++)
260259
{
261260
if(glyphInfo[glyphIndex].cluster != currentClusterValue)
262261
{
262+
//open a new cluster
263263
currentClusterValue = glyphInfo[glyphIndex].cluster;
264264
clusters[clusterCount] = currentClusterValue;
265-
clusterFirstGlyphs[clusterCount] = glyphIndex;
266265
clusterMetrics[clusterCount] = run->glyphs[glyphIndex].metrics;
267266
clusterCount++;
268267
}
269-
clusterMetrics[clusterCount] = oc_text_metrics_combine(clusterMetrics[clusterCount],
270-
run->glyphs[glyphIndex].metrics);
268+
else
269+
{
270+
//accumulate metrics in the current cluster
271+
clusterMetrics[clusterCount - 1] = oc_text_metrics_combine(clusterMetrics[clusterCount - 1],
272+
run->glyphs[glyphIndex].metrics);
273+
}
271274
}
272275

273276
//NOTE: bucket graphemes into clusters
274-
u64* clusterFirstGrapheme = oc_arena_push_array(scratch.arena, u64, clusterCount);
275-
u64* clusterGraphemeCount = oc_arena_push_array(scratch.arena, u64, clusterCount);
277+
u64* clusterFirstGraphemes = oc_arena_push_array(scratch.arena, u64, clusterCount);
278+
u64* clusterGraphemeCounts = oc_arena_push_array(scratch.arena, u64, clusterCount);
276279
u64 nextGrapheme = 0;
277280

278-
for(u64 clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++)
281+
hb_direction_t bufferDirection = hb_buffer_get_direction(buffer);
282+
oc_text_direction direction = OC_TEXT_DIRECTION_LTR;
283+
if(bufferDirection == HB_DIRECTION_RTL || bufferDirection == HB_DIRECTION_BTT)
284+
{
285+
direction = OC_TEXT_DIRECTION_RTL;
286+
}
287+
288+
for(i64 clusterIndex = (direction == OC_TEXT_DIRECTION_LTR) ? (0) : (clusterCount - 1);
289+
clusterIndex < clusterCount && clusterIndex >= 0;
290+
(direction == OC_TEXT_DIRECTION_LTR) ? (clusterIndex++) : (clusterIndex--))
279291
{
280-
clusterFirstGrapheme[clusterIndex] = nextGrapheme;
281-
clusterGraphemeCount[clusterIndex] = 0;
292+
//NOTE: graphemes potentially straddle several subsequent clusters. We advance to the next grapheme only if all codepoints
293+
// of the current grapheme are before the current cluster
294+
if(run->graphemes[nextGrapheme].offset + run->graphemes[nextGrapheme].count <= clusters[clusterIndex])
295+
{
296+
nextGrapheme++;
297+
}
298+
299+
clusterFirstGraphemes[clusterIndex] = nextGrapheme;
300+
clusterGraphemeCounts[clusterIndex] = 0;
301+
282302
for(u64 graphemeIndex = nextGrapheme; graphemeIndex < run->graphemeCount; graphemeIndex++)
283303
{
284-
if((clusterIndex < clusterCount - 1)
285-
&& run->graphemes[graphemeIndex].offset >= clusters[clusterIndex + 1])
304+
bool hasNextCluster = (direction == OC_TEXT_DIRECTION_LTR) ? (clusterIndex < clusterCount - 1) : (clusterIndex > 0);
305+
u64 nextClusterIndex = (direction == OC_TEXT_DIRECTION_LTR) ? (clusterIndex + 1) : (clusterIndex - 1);
306+
307+
if(hasNextCluster
308+
&& run->graphemes[graphemeIndex].offset >= clusters[nextClusterIndex])
286309
{
287310
//NOTE: End of current cluster.
288311
// Cluster contains clusterGraphemeCount graphemes starting at clusterFirstGrapheme
289-
nextGrapheme += clusterGraphemeCount[clusterIndex];
312+
// We advance to the last grapheme of the current cluster, which could straddle into the next cluster
313+
OC_DEBUG_ASSERT(clusterGraphemeCounts[clusterIndex]);
314+
nextGrapheme += clusterGraphemeCounts[clusterIndex] - 1;
290315
break;
291316
}
292317
else
293318
{
294-
// add graphemes to current cluster
295-
clusterGraphemeCount[clusterIndex]++;
319+
// add grapheme to current cluster
320+
clusterGraphemeCounts[clusterIndex]++;
296321
}
297322
}
298323
}
324+
bool* graphemeInit = oc_arena_push_array(scratch.arena, bool, run->graphemeCount);
325+
memset(graphemeInit, 0, sizeof(bool) * run->graphemeCount);
299326

300327
//NOTE: compute grapheme positions
301328
for(u64 clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++)
302329
{
303-
u64 firstGrapheme = clusterFirstGrapheme[clusterIndex];
304-
u64 graphemeCount = clusterGraphemeCount[clusterIndex];
330+
u64 clusterFirstGrapheme = clusterFirstGraphemes[clusterIndex];
331+
u64 clusterGraphemeCount = clusterGraphemeCounts[clusterIndex];
305332

306333
//TODO: we should try to use ligature caret position if they exist, using
307334
// hb_ot_layout_get_ligature_carets(). However, all latin fonts with ligatures
@@ -310,34 +337,45 @@ oc_glyph_run* oc_harfbuzz_font_shape(oc_arena* arena,
310337
// check with someone who does.
311338

312339
//NOTE: Fall back to evenly subdividing the cluster size.
313-
//////////////////////////////////////////////////////////////////////////////////////////////
314-
//TODO: this works only for LTR
315-
//////////////////////////////////////////////////////////////////////////////////////////////
316-
317340
oc_text_metrics metrics = clusterMetrics[clusterIndex];
318-
oc_vec2 advance = oc_vec2_mul(1. / graphemeCount, metrics.advance);
341+
oc_vec2 advance = oc_vec2_mul(1. / clusterGraphemeCount, metrics.advance);
319342

320-
for(u64 graphemeIndex = 0; graphemeIndex < graphemeCount; graphemeIndex++)
343+
for(u64 clusterGraphemeIndex = 0; clusterGraphemeIndex < clusterGraphemeCount; clusterGraphemeIndex++)
321344
{
322-
oc_vec2 offset = oc_vec2_mul(graphemeIndex, advance);
323-
324-
oc_text_metrics* graphemeMetrics = &run->graphemes[firstGrapheme + graphemeIndex].metrics;
325-
326-
graphemeMetrics->ink = (oc_rect){
327-
metrics.ink.x + graphemeIndex * advance.x,
328-
metrics.ink.y,
329-
metrics.ink.w / graphemeCount,
330-
metrics.ink.h,
345+
u64 graphemeIndex = clusterFirstGrapheme + clusterGraphemeIndex;
346+
347+
oc_text_metrics* graphemeMetrics = &run->graphemes[graphemeIndex].metrics;
348+
349+
//TODO: this works only for horizontal (LTR or RTL)layout, revise when we actually support vertical layout!
350+
oc_text_metrics localMetrics = {
351+
.ink = {
352+
metrics.ink.x + clusterGraphemeIndex * advance.x,
353+
metrics.ink.y,
354+
metrics.ink.w / clusterGraphemeCount,
355+
metrics.ink.h,
356+
},
357+
.logical = {
358+
metrics.logical.x + clusterGraphemeIndex * advance.x,
359+
metrics.logical.y,
360+
metrics.logical.w / clusterGraphemeCount,
361+
metrics.logical.h,
362+
},
363+
.advance = advance,
331364
};
332365

333-
graphemeMetrics->logical = (oc_rect){
334-
metrics.logical.x + graphemeIndex * advance.x,
335-
metrics.logical.y,
336-
metrics.logical.w / graphemeCount,
337-
metrics.logical.h,
338-
};
366+
bool isInit = graphemeInit[graphemeIndex];
339367

340-
graphemeMetrics->advance = advance;
368+
if(!isInit)
369+
{
370+
*graphemeMetrics = localMetrics;
371+
graphemeInit[graphemeIndex] = true;
372+
}
373+
else
374+
{
375+
//NOTE: if a grapheme has already been encountered (i.e. it straddles several clusters),
376+
// we expand its metrics rather than reset them
377+
*graphemeMetrics = oc_text_metrics_combine(*graphemeMetrics, localMetrics);
378+
}
341379
}
342380
}
343381
oc_scratch_end(scratch);
@@ -450,6 +488,33 @@ void oc_text_draw_run(oc_glyph_run* run, f32 fontSize)
450488

451489
f32 flipY = (context->textFlip) ? -1 : 1;
452490

491+
oc_vec2 origin = pos;
492+
493+
oc_set_width(2);
494+
oc_set_color_rgba(1, 0, 0, 1);
495+
for(u64 i = 0; i < run->graphemeCount; i++)
496+
{
497+
oc_grapheme_info* grapheme = &run->graphemes[i];
498+
oc_rectangle_stroke(origin.x + grapheme->metrics.logical.x * scale,
499+
origin.y + grapheme->metrics.logical.y * scale,
500+
grapheme->metrics.logical.w * scale,
501+
grapheme->metrics.logical.h * scale);
502+
}
503+
/*
504+
oc_set_color_rgba(0, 0, 1, 1);
505+
oc_rectangle_stroke(origin.x + run->metrics.logical.x * scale,
506+
origin.y + run->metrics.logical.y * scale,
507+
run->metrics.logical.w * scale,
508+
run->metrics.logical.h * scale);
509+
510+
oc_set_color_rgba(0, 1, 0, 1);
511+
oc_rectangle_stroke(origin.x + run->metrics.ink.x * scale,
512+
origin.y + run->metrics.ink.y * scale,
513+
run->metrics.ink.w * scale,
514+
run->metrics.ink.h * scale);
515+
*/
516+
oc_set_color_rgba(0, 0, 0, 1);
517+
453518
for(u64 i = 0; i < run->glyphCount; i++)
454519
{
455520
oc_glyph_info* glyph = &run->glyphs[i];
@@ -464,6 +529,13 @@ void oc_text_draw_run(oc_glyph_run* run, f32 fontSize)
464529

465530
hb_font_draw_glyph(harfbuzzFont->hbFont, glyph->index, oc_hbDrawFuncs, &data);
466531

532+
/*
533+
oc_set_width(1);
534+
oc_rectangle_stroke(origin.x + glyph->metrics.logical.x * scale,
535+
origin.y + glyph->metrics.logical.y * scale,
536+
glyph->metrics.logical.w * scale,
537+
glyph->metrics.logical.h * scale);
538+
*/
467539
pos.x += glyph->metrics.advance.x * scale;
468540
pos.y += glyph->metrics.advance.y * scale * flipY;
469541
}

0 commit comments

Comments
 (0)