summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-06-30 08:23:11 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-06-30 08:23:11 +0300
commit2c82b88220bf9f0592b0015d1c0099d126249522 (patch)
tree281278054dd46df892f3399c670bcaed3436931e
parent73a721fc93c3be7b13361dea41d4431ad14a3fdd (diff)
Text: Use HarfBuzz to shape text
This kind of already works! HarfBuzz will composite glyphs as expected. Still missing: half-pixel offsets, line wrapping, color escapes, monospace grid alignment. FriBidi will still be required to determine/reorder text direction within each run.
-rw-r--r--CMakeLists.txt6
-rw-r--r--src/ui/text.c325
-rw-r--r--src/ui/text_simple.c38
3 files changed, 286 insertions, 83 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e6baba76..b62517c9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -311,6 +311,12 @@ target_link_libraries (app PUBLIC the_Foundation::the_Foundation)
311target_link_libraries (app PUBLIC ${SDL2_LDFLAGS}) 311target_link_libraries (app PUBLIC ${SDL2_LDFLAGS})
312if (ENABLE_HARFBUZZ AND HARFBUZZ_FOUND) 312if (ENABLE_HARFBUZZ AND HARFBUZZ_FOUND)
313 target_link_libraries (app PUBLIC harfbuzz) 313 target_link_libraries (app PUBLIC harfbuzz)
314 # HarfBuzz is C++ so must link with the standard library.
315 if (APPLE)
316 target_link_libraries (app PUBLIC c++)
317 else ()
318 target_link_libraries (app PUBLIC stdc++)
319 endif ()
314 target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_HARFBUZZ=1) 320 target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_HARFBUZZ=1)
315endif () 321endif ()
316if (APPLE) 322if (APPLE)
diff --git a/src/ui/text.c b/src/ui/text.c
index c851be90..6cfb5c51 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -76,17 +76,17 @@ enum iGlyphFlag {
76struct Impl_Glyph { 76struct Impl_Glyph {
77 iHashNode node; 77 iHashNode node;
78 int flags; 78 int flags;
79 uint32_t glyphIndex; 79// uint32_t glyphIndex;
80 iFont *font; /* may come from symbols/emoji */ 80 iFont *font; /* may come from symbols/emoji */
81 iRect rect[2]; /* zero and half pixel offset */ 81 iRect rect[2]; /* zero and half pixel offset */
82 iInt2 d[2]; 82 iInt2 d[2];
83 float advance; /* scaled */ 83 float advance; /* scaled */
84}; 84};
85 85
86void init_Glyph(iGlyph *d, iChar ch) { 86void init_Glyph(iGlyph *d, uint32_t glyphIndex) {
87 d->node.key = ch; 87 d->node.key = glyphIndex;
88 d->flags = 0; 88 d->flags = 0;
89 d->glyphIndex = 0; 89 //d->glyphIndex = 0;
90 d->font = NULL; 90 d->font = NULL;
91 d->rect[0] = zero_Rect(); 91 d->rect[0] = zero_Rect();
92 d->rect[1] = zero_Rect(); 92 d->rect[1] = zero_Rect();
@@ -97,7 +97,10 @@ void deinit_Glyph(iGlyph *d) {
97 iUnused(d); 97 iUnused(d);
98} 98}
99 99
100static iChar codepoint_Glyph_(const iGlyph *d) { 100//static iChar codepoint_Glyph_(const iGlyph *d) {
101// return d->node.key;
102//}
103static uint32_t index_Glyph_(const iGlyph *d) {
101 return d->node.key; 104 return d->node.key;
102} 105}
103 106
@@ -126,11 +129,16 @@ struct Impl_Font {
126 int vertOffset; /* offset due to scaling */ 129 int vertOffset; /* offset due to scaling */
127 int height; 130 int height;
128 int baseline; 131 int baseline;
129 iHash glyphs; 132 iHash glyphs; /* key is glyph index in the font */ /* TODO: does not need to be a Hash */
130 iBool isMonospaced; 133 iBool isMonospaced;
131 iBool manualKernOnly; 134 iBool manualKernOnly;
132 enum iFontSize sizeId; /* used to look up different fonts of matching size */ 135 enum iFontSize sizeId; /* used to look up different fonts of matching size */
133 uint32_t indexTable[128 - 32]; /* quick ASCII lookup */ 136 uint32_t indexTable[128 - 32]; /* quick ASCII lookup */
137#if defined (LAGRANGE_ENABLE_HARFBUZZ)
138 hb_blob_t * hbBlob; /* raw TrueType data */
139 hb_face_t * hbFace;
140 hb_font_t * hbFont;
141#endif
134}; 142};
135 143
136static iFont *font_Text_(enum iFontId id); 144static iFont *font_Text_(enum iFontId id);
@@ -178,6 +186,14 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale,
178 } 186 }
179 d->sizeId = sizeId; 187 d->sizeId = sizeId;
180 memset(d->indexTable, 0xff, sizeof(d->indexTable)); 188 memset(d->indexTable, 0xff, sizeof(d->indexTable));
189#if defined(LAGRANGE_ENABLE_HARFBUZZ)
190 /* HarfBuzz will read the font data. */ {
191 d->hbBlob = hb_blob_create(constData_Block(data), size_Block(data),
192 HB_MEMORY_MODE_READONLY, NULL, NULL);
193 d->hbFace = hb_face_create(d->hbBlob, 0);
194 d->hbFont = hb_font_create(d->hbFace);
195 }
196#endif
181} 197}
182 198
183static void clearGlyphs_Font_(iFont *d) { 199static void clearGlyphs_Font_(iFont *d) {
@@ -188,6 +204,13 @@ static void clearGlyphs_Font_(iFont *d) {
188} 204}
189 205
190static void deinit_Font(iFont *d) { 206static void deinit_Font(iFont *d) {
207#if defined(LAGRANGE_ENABLE_HARFBUZZ)
208 /* HarfBuzz objects. */ {
209 hb_font_destroy(d->hbFont);
210 hb_face_destroy(d->hbFace);
211 hb_blob_destroy(d->hbBlob);
212 }
213#endif
191 clearGlyphs_Font_(d); 214 clearGlyphs_Font_(d);
192 deinit_Hash(&d->glyphs); 215 deinit_Hash(&d->glyphs);
193 delete_Block(d->data); 216 delete_Block(d->data);
@@ -570,7 +593,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
570 iRect *glRect = &glyph->rect[hoff]; 593 iRect *glRect = &glyph->rect[hoff];
571 int x0, y0, x1, y1; 594 int x0, y0, x1, y1;
572 stbtt_GetGlyphBitmapBoxSubpixel( 595 stbtt_GetGlyphBitmapBoxSubpixel(
573 &d->font, glyph->glyphIndex, d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1); 596 &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1);
574 glRect->size = init_I2(x1 - x0, y1 - y0); 597 glRect->size = init_I2(x1 - x0, y1 - y0);
575 /* Determine placement in the glyph cache texture, advancing in rows. */ 598 /* Determine placement in the glyph cache texture, advancing in rows. */
576 glRect->pos = assignCachePos_Text_(&text_, glRect->size); 599 glRect->pos = assignCachePos_Text_(&text_, glRect->size);
@@ -578,8 +601,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
578 glyph->d[hoff].y += d->vertOffset; 601 glyph->d[hoff].y += d->vertOffset;
579 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */ 602 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */
580 int adv; 603 int adv;
581 const uint32_t gIndex = glyph->glyphIndex; 604 stbtt_GetGlyphHMetrics(&d->font, index_Glyph_(glyph), &adv, NULL);
582 stbtt_GetGlyphHMetrics(&d->font, gIndex, &adv, NULL);
583 glyph->advance = d->xScale * adv; 605 glyph->advance = d->xScale * adv;
584 } 606 }
585} 607}
@@ -670,12 +692,9 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
670 return d; 692 return d;
671} 693}
672 694
673static iGlyph *glyph_Font_(iFont *d, iChar ch) { 695static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
674 iGlyph * glyph; 696 iGlyph* glyph = NULL;
675 uint32_t glyphIndex = 0; 697 void * node = value_Hash(&d->glyphs, glyphIndex);
676 /* The glyph may actually come from a different font; look up the right font. */
677 iFont *font = characterFont_Font_(d, ch, &glyphIndex);
678 void * node = value_Hash(&font->glyphs, ch);
679 if (node) { 698 if (node) {
680 glyph = node; 699 glyph = node;
681 } 700 }
@@ -687,18 +706,24 @@ static iGlyph *glyph_Font_(iFont *d, iChar ch) {
687#endif 706#endif
688 resetCache_Text_(&text_); 707 resetCache_Text_(&text_);
689 } 708 }
690 glyph = new_Glyph(ch); 709 glyph = new_Glyph(glyphIndex);
691 glyph->glyphIndex = glyphIndex; 710 glyph->font = d;
692 glyph->font = font;
693 /* New glyphs are always allocated at least. This reserves a position in the cache 711 /* New glyphs are always allocated at least. This reserves a position in the cache
694 and updates the glyph metrics. */ 712 and updates the glyph metrics. */
695 allocate_Font_(font, glyph, 0); 713 allocate_Font_(d, glyph, 0);
696 allocate_Font_(font, glyph, 1); 714 allocate_Font_(d, glyph, 1);
697 insert_Hash(&font->glyphs, &glyph->node); 715 insert_Hash(&d->glyphs, &glyph->node);
698 } 716 }
699 return glyph; 717 return glyph;
700} 718}
701 719
720static iGlyph *glyph_Font_(iFont *d, iChar ch) {
721 /* The glyph may actually come from a different font; look up the right font. */
722 uint32_t glyphIndex = 0;
723 iFont *font = characterFont_Font_(d, ch, &glyphIndex);
724 return glyphByIndex_Font_(font, glyphIndex);
725}
726
702static iChar nextChar_(const char **chPos, const char *end) { 727static iChar nextChar_(const char **chPos, const char *end) {
703 if (*chPos == end) { 728 if (*chPos == end) {
704 return 0; 729 return 0;
@@ -713,6 +738,76 @@ static iChar nextChar_(const char **chPos, const char *end) {
713 return ch; 738 return ch;
714} 739}
715 740
741/*----------------------------------------------------------------------------------------------*/
742
743iDeclareType(AttributedRun)
744
745struct Impl_AttributedRun {
746 iRangecc text;
747 iFont *font;
748 iColor fgColor;
749};
750
751iDeclareType(AttributedText)
752iDeclareTypeConstructionArgs(AttributedText, iRangecc text, iFont *font, iColor fgColor)
753
754struct Impl_AttributedText {
755 iRangecc text;
756 iFont *font;
757 iColor fgColor;
758 iArray runs;
759};
760
761iDefineTypeConstructionArgs(AttributedText, (iRangecc text, iFont *font, iColor fgColor),
762 text, font, fgColor)
763
764static void prepare_AttributedText_(iAttributedText *d) {
765 iAssert(isEmpty_Array(&d->runs));
766 const char *chPos = d->text.start;
767 iAttributedRun run = { .text = d->text, .font = d->font, .fgColor = d->fgColor };
768 while (chPos < d->text.end) {
769 const char *currentPos = chPos;
770 const iChar ch = nextChar_(&chPos, d->text.end);
771 if (ch == '\v') {
772 /* TODO: Color escapes. */
773
774 }
775 if (isSpace_Char(ch) || isVariationSelector_Char(ch) || isDefaultIgnorable_Char(ch) ||
776 isFitzpatrickType_Char(ch)) {
777 continue;
778 }
779 const iGlyph *glyph = glyph_Font_(d->font, ch);
780 /* TODO: Look for ANSI/color escapes. */
781 if (glyph->font != run.font) {
782 /* A different font is being used for this glyph. */
783 iAttributedRun finishedRun = run;
784 finishedRun.text.end = currentPos;
785 if (!isEmpty_Range(&finishedRun.text)) {
786 pushBack_Array(&d->runs, &finishedRun);
787 }
788 run.text.start = currentPos;
789 run.font = glyph->font;
790 }
791 }
792 if (!isEmpty_Range(&run.text)) {
793 pushBack_Array(&d->runs, &run);
794 }
795}
796
797void init_AttributedText(iAttributedText *d, iRangecc text, iFont *font, iColor fgColor) {
798 d->text = text;
799 d->font = font;
800 d->fgColor = fgColor;
801 init_Array(&d->runs, sizeof(iAttributedRun));
802 prepare_AttributedText_(d);
803}
804
805void deinit_AttributedText(iAttributedText *d) {
806 deinit_Array(&d->runs);
807}
808
809/*----------------------------------------------------------------------------------------------*/
810
716iDeclareType(RasterGlyph) 811iDeclareType(RasterGlyph)
717 812
718struct Impl_RasterGlyph { 813struct Impl_RasterGlyph {
@@ -721,35 +816,40 @@ struct Impl_RasterGlyph {
721 iRect rect; 816 iRect rect;
722}; 817};
723 818
724void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { 819static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
725 const char * chPos = text.start; 820 /* TODO: Make this an object so it can be used sequentially without reallocating buffers. */
821// const char * chPos = text.start;
726 SDL_Surface *buf = NULL; 822 SDL_Surface *buf = NULL;
727 const iInt2 bufSize = init_I2(iMin(512, d->height * iMin(2 * size_Range(&text), 20)), 823 const iInt2 bufSize = init_I2(iMin(512, d->height * iMin(2 * size_Array(glyphIndices), 20)),
728 d->height * 4 / 3); 824 d->height * 4 / 3);
729 int bufX = 0; 825 int bufX = 0;
730 iArray * rasters = NULL; 826 iArray * rasters = NULL;
731 SDL_Texture *oldTarget = NULL; 827 SDL_Texture *oldTarget = NULL;
732 iBool isTargetChanged = iFalse; 828 iBool isTargetChanged = iFalse;
733 iAssert(isExposed_Window(get_Window())); 829 iAssert(isExposed_Window(get_Window()));
830// iAttributedText *attrText = new_AttributedText(text, d, (iColor){ 255, 255, 255, 255 });
734 /* We'll flush the buffered rasters periodically until everything is cached. */ 831 /* We'll flush the buffered rasters periodically until everything is cached. */
735 while (chPos < text.end) { 832 size_t index = 0;
736 while (chPos < text.end) { 833 while (index < size_Array(glyphIndices)) {
737 const char *lastPos = chPos; 834 for (; index < size_Array(glyphIndices); index++) {
738 const iChar ch = nextChar_(&chPos, text.end); 835// const char *lastPos = chPos;
739 if (ch == 0 || isSpace_Char(ch) || isDefaultIgnorable_Char(ch) || 836// const iChar ch = nextChar_(&chPos, text.end);
740 isFitzpatrickType_Char(ch)) { 837// if (ch == 0 || isSpace_Char(ch) || isDefaultIgnorable_Char(ch) ||
741 continue; 838// isFitzpatrickType_Char(ch)) {
742 } 839// continue;
840// }
841 const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t);
743 const int lastCacheBottom = text_.cacheBottom; 842 const int lastCacheBottom = text_.cacheBottom;
744 iGlyph *glyph = glyph_Font_(d, ch); 843 iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex);
745 if (text_.cacheBottom < lastCacheBottom) { 844 if (text_.cacheBottom < lastCacheBottom) {
746 /* The cache was reset due to running out of space. We need to restart from 845 /* The cache was reset due to running out of space. We need to restart from
747 the beginning! */ 846 the beginning! */
748 chPos = text.start;
749 bufX = 0; 847 bufX = 0;
750 if (rasters) { 848 if (rasters) {
751 clear_Array(rasters); 849 clear_Array(rasters);
752 } 850 }
851 index = 0;
852 break;
753 } 853 }
754 if (!isFullyRasterized_Glyph_(glyph)) { 854 if (!isFullyRasterized_Glyph_(glyph)) {
755 /* Need to cache this. */ 855 /* Need to cache this. */
@@ -764,9 +864,9 @@ void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {
764 } 864 }
765 SDL_Surface *surfaces[2] = { 865 SDL_Surface *surfaces[2] = {
766 !isRasterized_Glyph_(glyph, 0) ? 866 !isRasterized_Glyph_(glyph, 0) ?
767 rasterizeGlyph_Font_(glyph->font, glyph->glyphIndex, 0) : NULL, 867 rasterizeGlyph_Font_(glyph->font, index_Glyph_(glyph), 0) : NULL,
768 !isRasterized_Glyph_(glyph, 1) ? 868 !isRasterized_Glyph_(glyph, 1) ?
769 rasterizeGlyph_Font_(glyph->font, glyph->glyphIndex, 0.5f) : NULL 869 rasterizeGlyph_Font_(glyph->font, index_Glyph_(glyph), 0.5f) : NULL
770 }; 870 };
771 iBool outOfSpace = iFalse; 871 iBool outOfSpace = iFalse;
772 iForIndices(i, surfaces) { 872 iForIndices(i, surfaces) {
@@ -797,7 +897,7 @@ void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {
797 } 897 }
798 } 898 }
799 if (outOfSpace) { 899 if (outOfSpace) {
800 chPos = lastPos; 900 index--; /* do-over */
801 break; 901 break;
802 } 902 }
803 } 903 }
@@ -828,10 +928,8 @@ void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {
828 clear_Array(rasters); 928 clear_Array(rasters);
829 bufX = 0; 929 bufX = 0;
830 } 930 }
831 else {
832 iAssert(chPos >= text.end);
833 }
834 } 931 }
932// delete_AttributedText(attrText);
835 if (rasters) { 933 if (rasters) {
836 delete_Array(rasters); 934 delete_Array(rasters);
837 } 935 }
@@ -843,6 +941,30 @@ void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {
843 } 941 }
844} 942}
845 943
944static void cacheSingleGlyph_Font_(iFont *d, uint32_t glyphIndex) {
945 iArray indices;
946 init_Array(&indices, sizeof(uint32_t));
947 pushBack_Array(&indices, &glyphIndex);
948 cacheGlyphs_Font_(d, &indices);
949 deinit_Array(&indices);
950}
951
952static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) {
953 iArray glyphIndices;
954 init_Array(&glyphIndices, sizeof(uint32_t));
955 for (const char *chPos = text.start; chPos != text.end; ) {
956 const char *oldPos = chPos;
957 const iChar ch = nextChar_(&chPos, text.end);
958 if (chPos == oldPos) break;
959 const uint32_t glyphIndex = glyphIndex_Font_(d, ch);
960 if (glyphIndex) {
961 pushBack_Array(&glyphIndices, &glyphIndex);
962 }
963 }
964 cacheGlyphs_Font_(d, &glyphIndices);
965 deinit_Array(&glyphIndices);
966}
967
846enum iRunMode { 968enum iRunMode {
847 measure_RunMode = 0, 969 measure_RunMode = 0,
848 draw_RunMode = 1, 970 draw_RunMode = 1,
@@ -861,44 +983,6 @@ static enum iFontId fontId_Text_(const iFont *font) {
861 return (enum iFontId) (font - text_.fonts); 983 return (enum iFontId) (font - text_.fonts);
862} 984}
863 985
864iLocalDef iBool isWrapPunct_(iChar c) {
865 /* Punctuation that participates in word-wrapping. */
866 return (c == '/' || c == '\\' || c == '=' || c == '-' || c == ',' || c == ';' || c == '.' || c == ':' || c == 0xad);
867}
868
869iLocalDef iBool isClosingBracket_(iChar c) {
870 return (c == ')' || c == ']' || c == '}' || c == '>');
871}
872
873//iLocalDef iBool isBracket_(iChar c) {
874// return (c == '(' || c == '[' || c == '{' || c == '<' || isClosingBracket_(c));
875//}
876
877iLocalDef iBool isWrapBoundary_(iChar prevC, iChar c) {
878 /* Line wrapping boundaries are determined by looking at a character and the
879 last character processed. We want to wrap at natural word boundaries where
880 possible, so normally we wrap at a space followed a non-space character. As
881 an exception, we also wrap after punctuation used to break up words, so we
882 can wrap text like foo/bar/baz-abc-def.xyz at any puncation boundaries,
883 without wrapping on other punctuation used for expressive purposes like
884 emoticons :-) */
885 if (isClosingBracket_(prevC) && !isWrapPunct_(c)) {
886 return iTrue;
887 }
888 if (isSpace_Char(prevC)) {
889 return iFalse;
890 }
891 if ((prevC == '/' || prevC == '\\' || prevC == '-' || prevC == '_' || prevC == '+') &&
892 !isWrapPunct_(c)) {
893 return iTrue;
894 }
895 return isSpace_Char(c);
896}
897
898iLocalDef iBool isMeasuring_(enum iRunMode mode) {
899 return (mode & modeMask_RunMode) == measure_RunMode;
900}
901
902iDeclareType(RunArgs) 986iDeclareType(RunArgs)
903 987
904struct Impl_RunArgs { 988struct Impl_RunArgs {
@@ -915,15 +999,94 @@ struct Impl_RunArgs {
915 999
916#if defined (LAGRANGE_ENABLE_HARFBUZZ) 1000#if defined (LAGRANGE_ENABLE_HARFBUZZ)
917static iRect run_Font_(iFont *d, const iRunArgs *args) { 1001static iRect run_Font_(iFont *d, const iRunArgs *args) {
918 iRect bounds = zero_Rect(); 1002 const int mode = args->mode;
919 const iInt2 orig = args->pos; 1003 iRect bounds = zero_Rect();
1004 const iInt2 orig = args->pos;
1005 float xCursor = 0.0f;
1006 float yCursor = 0.0f;
1007 float xCursorMax = 0.0f;
1008 iAssert(args->text.end >= args->text.start);
1009 if (args->continueFrom_out) {
1010 *args->continueFrom_out = args->text.end;
1011 }
1012 hb_buffer_t *hbBuf = hb_buffer_create();
1013 /* Split the text into a number of attributed runs that specify exactly which font is
1014 used and other attributes such as color. (HarfBuzz shaping is done with one specific font.) */
1015 iAttributedText *attrText = new_AttributedText(args->text, d, get_Color(args->color));
1016 iConstForEach(Array, i, &attrText->runs) {
1017 const iAttributedRun *run = i.value;
1018 hb_buffer_clear_contents(hbBuf);
1019 hb_buffer_add_utf8(hbBuf, run->text.start, size_Range(&run->text), 0, -1);
1020 hb_buffer_set_direction(hbBuf, HB_DIRECTION_LTR); /* TODO: FriBidi? */
1021 /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */
1022 hb_buffer_set_language(hbBuf, hb_language_from_string("en", -1)); /* TODO: language from document/UI, if known */
1023 hb_shape(run->font->hbFont, hbBuf, NULL, 0); /* TODO: Specify features, too? */
1024 unsigned int glyphCount = 0;
1025 const hb_glyph_info_t * glyphInfo = hb_buffer_get_glyph_infos(hbBuf, &glyphCount);
1026 const hb_glyph_position_t *glyphPos = hb_buffer_get_glyph_positions(hbBuf, &glyphCount);
1027 /* Draw each glyph. */
1028 for (unsigned int i = 0; i < glyphCount; i++) {
1029 const hb_codepoint_t glyphId = glyphInfo[i].codepoint;
1030 const float xOffset = run->font->xScale * glyphPos[i].x_offset;
1031 const float yOffset = run->font->yScale * glyphPos[i].y_offset;
1032 const float xAdvance = run->font->xScale * glyphPos[i].x_advance;
1033 const float yAdvance = run->font->yScale * glyphPos[i].y_advance;
1034 const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId);
1035 const int hoff = 0; /* TODO: which half? */
1036 /* draw_glyph(glyphid, cursor_x + x_offset, cursor_y + y_offset); */
1037 /* Draw the glyph. */ {
1038 SDL_Rect dst = { orig.x + xCursor + xOffset + glyph->d[hoff].x,
1039 orig.y + yCursor + yOffset + glyph->font->baseline + glyph->d[hoff].y,
1040 glyph->rect[hoff].size.x,
1041 glyph->rect[hoff].size.y };
1042 if (mode & visualFlag_RunMode) {
1043 if (isEmpty_Rect(bounds)) {
1044 bounds = init_Rect(dst.x, dst.y, dst.w, dst.h);
1045 }
1046 else {
1047 bounds = union_Rect(bounds, init_Rect(dst.x, dst.y, dst.w, dst.h));
1048 }
1049 }
1050 else {
1051 bounds.size.x = iMax(bounds.size.x, dst.x + dst.w);
1052 bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height);
1053 }
1054 if (mode & draw_RunMode) {
1055 if (!isRasterized_Glyph_(glyph, hoff)) {
1056 cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */
1057 glyph = glyphByIndex_Font_(run->font, glyphId);
1058 iAssert(isRasterized_Glyph_(glyph, hoff));
1059 }
1060 SDL_Rect src;
1061 memcpy(&src, &glyph->rect[hoff], sizeof(SDL_Rect));
1062 if (args->mode & fillBackground_RunMode) {
1063 /* Alpha blending looks much better if the RGB components don't change in
1064 the partially transparent pixels. */
1065 SDL_RenderFillRect(text_.render, &dst);
1066 }
1067 SDL_RenderCopy(text_.render, text_.cache, &src, &dst);
1068 }
1069 }
1070 xCursor += xAdvance;
1071 yCursor += yAdvance;
1072 xCursorMax = iMax(xCursorMax, xCursor);
1073 }
1074 }
1075 if (args->runAdvance_out) {
1076 *args->runAdvance_out = xCursorMax;
1077 }
1078 hb_buffer_destroy(hbBuf);
1079 delete_AttributedText(attrText);
920 return bounds; 1080 return bounds;
921} 1081}
1082
922#else /* !defined (LAGRANGE_ENABLE_HARFBUZZ) */ 1083#else /* !defined (LAGRANGE_ENABLE_HARFBUZZ) */
1084
923/* The fallback method: an incomplete solution for simple scripts. */ 1085/* The fallback method: an incomplete solution for simple scripts. */
924# define run_Font_ runSimple_Font_ 1086# define run_Font_ runSimple_Font_
925# include "text_simple.c" 1087# include "text_simple.c"
926#endif 1088
1089#endif /* defined (LAGRANGE_ENABLE_HARFBUZZ) */
927 1090
928int lineHeight_Text(int fontId) { 1091int lineHeight_Text(int fontId) {
929 return font_Text_(fontId)->height; 1092 return font_Text_(fontId)->height;
diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c
index baa87e4b..575d00cb 100644
--- a/src/ui/text_simple.c
+++ b/src/ui/text_simple.c
@@ -22,10 +22,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23/* this file is included from text.c, so it doesn't use includes of its own */ 23/* this file is included from text.c, so it doesn't use includes of its own */
24 24
25iLocalDef iBool isWrapPunct_(iChar c) {
26 /* Punctuation that participates in word-wrapping. */
27 return (c == '/' || c == '\\' || c == '=' || c == '-' || c == ',' || c == ';' || c == '.' || c == ':' || c == 0xad);
28}
29
30iLocalDef iBool isClosingBracket_(iChar c) {
31 return (c == ')' || c == ']' || c == '}' || c == '>');
32}
33
34iLocalDef iBool isWrapBoundary_(iChar prevC, iChar c) {
35 /* Line wrapping boundaries are determined by looking at a character and the
36 last character processed. We want to wrap at natural word boundaries where
37 possible, so normally we wrap at a space followed a non-space character. As
38 an exception, we also wrap after punctuation used to break up words, so we
39 can wrap text like foo/bar/baz-abc-def.xyz at any puncation boundaries,
40 without wrapping on other punctuation used for expressive purposes like
41 emoticons :-) */
42 if (isClosingBracket_(prevC) && !isWrapPunct_(c)) {
43 return iTrue;
44 }
45 if (isSpace_Char(prevC)) {
46 return iFalse;
47 }
48 if ((prevC == '/' || prevC == '\\' || prevC == '-' || prevC == '_' || prevC == '+') &&
49 !isWrapPunct_(c)) {
50 return iTrue;
51 }
52 return isSpace_Char(c);
53}
54
55iLocalDef iBool isMeasuring_(enum iRunMode mode) {
56 return (mode & modeMask_RunMode) == measure_RunMode;
57}
58
25static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { 59static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
26 /* This function shapes text using a simplified, incomplete algorithm. It works for English 60 /* This function shapes text using a simplified, incomplete algorithm. It works for English
27 and other simple LTR scripts. Composed glyphs are not supported (must rely on text being 61 and other non-complex LTR scripts. Composed glyphs are not supported (must rely on text
28 in a pre-composed form). This algorithm is used if HarfBuzz is not available. */ 62 being in a pre-composed form). This algorithm is used if HarfBuzz is not available. */
29 iRect bounds = zero_Rect(); 63 iRect bounds = zero_Rect();
30 const iInt2 orig = args->pos; 64 const iInt2 orig = args->pos;
31 float xpos = orig.x; 65 float xpos = orig.x;