diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-06-30 08:23:11 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-06-30 08:23:11 +0300 |
commit | 2c82b88220bf9f0592b0015d1c0099d126249522 (patch) | |
tree | 281278054dd46df892f3399c670bcaed3436931e | |
parent | 73a721fc93c3be7b13361dea41d4431ad14a3fdd (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.txt | 6 | ||||
-rw-r--r-- | src/ui/text.c | 325 | ||||
-rw-r--r-- | src/ui/text_simple.c | 38 |
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) | |||
311 | target_link_libraries (app PUBLIC ${SDL2_LDFLAGS}) | 311 | target_link_libraries (app PUBLIC ${SDL2_LDFLAGS}) |
312 | if (ENABLE_HARFBUZZ AND HARFBUZZ_FOUND) | 312 | if (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) |
315 | endif () | 321 | endif () |
316 | if (APPLE) | 322 | if (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 { | |||
76 | struct Impl_Glyph { | 76 | struct 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 | ||
86 | void init_Glyph(iGlyph *d, iChar ch) { | 86 | void 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 | ||
100 | static iChar codepoint_Glyph_(const iGlyph *d) { | 100 | //static iChar codepoint_Glyph_(const iGlyph *d) { |
101 | // return d->node.key; | ||
102 | //} | ||
103 | static 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 | ||
136 | static iFont *font_Text_(enum iFontId id); | 144 | static 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 | ||
183 | static void clearGlyphs_Font_(iFont *d) { | 199 | static void clearGlyphs_Font_(iFont *d) { |
@@ -188,6 +204,13 @@ static void clearGlyphs_Font_(iFont *d) { | |||
188 | } | 204 | } |
189 | 205 | ||
190 | static void deinit_Font(iFont *d) { | 206 | static 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 | ||
673 | static iGlyph *glyph_Font_(iFont *d, iChar ch) { | 695 | static 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 | ||
720 | static 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 | |||
702 | static iChar nextChar_(const char **chPos, const char *end) { | 727 | static 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 | |||
743 | iDeclareType(AttributedRun) | ||
744 | |||
745 | struct Impl_AttributedRun { | ||
746 | iRangecc text; | ||
747 | iFont *font; | ||
748 | iColor fgColor; | ||
749 | }; | ||
750 | |||
751 | iDeclareType(AttributedText) | ||
752 | iDeclareTypeConstructionArgs(AttributedText, iRangecc text, iFont *font, iColor fgColor) | ||
753 | |||
754 | struct Impl_AttributedText { | ||
755 | iRangecc text; | ||
756 | iFont *font; | ||
757 | iColor fgColor; | ||
758 | iArray runs; | ||
759 | }; | ||
760 | |||
761 | iDefineTypeConstructionArgs(AttributedText, (iRangecc text, iFont *font, iColor fgColor), | ||
762 | text, font, fgColor) | ||
763 | |||
764 | static 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 | |||
797 | void 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 | |||
805 | void deinit_AttributedText(iAttributedText *d) { | ||
806 | deinit_Array(&d->runs); | ||
807 | } | ||
808 | |||
809 | /*----------------------------------------------------------------------------------------------*/ | ||
810 | |||
716 | iDeclareType(RasterGlyph) | 811 | iDeclareType(RasterGlyph) |
717 | 812 | ||
718 | struct Impl_RasterGlyph { | 813 | struct Impl_RasterGlyph { |
@@ -721,35 +816,40 @@ struct Impl_RasterGlyph { | |||
721 | iRect rect; | 816 | iRect rect; |
722 | }; | 817 | }; |
723 | 818 | ||
724 | void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { | 819 | static 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 | ||
944 | static 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 | |||
952 | static 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 | |||
846 | enum iRunMode { | 968 | enum 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 | ||
864 | iLocalDef 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 | |||
869 | iLocalDef 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 | |||
877 | iLocalDef 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 | |||
898 | iLocalDef iBool isMeasuring_(enum iRunMode mode) { | ||
899 | return (mode & modeMask_RunMode) == measure_RunMode; | ||
900 | } | ||
901 | |||
902 | iDeclareType(RunArgs) | 986 | iDeclareType(RunArgs) |
903 | 987 | ||
904 | struct Impl_RunArgs { | 988 | struct Impl_RunArgs { |
@@ -915,15 +999,94 @@ struct Impl_RunArgs { | |||
915 | 999 | ||
916 | #if defined (LAGRANGE_ENABLE_HARFBUZZ) | 1000 | #if defined (LAGRANGE_ENABLE_HARFBUZZ) |
917 | static iRect run_Font_(iFont *d, const iRunArgs *args) { | 1001 | static 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 | ||
928 | int lineHeight_Text(int fontId) { | 1091 | int 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 | ||
25 | iLocalDef 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 | |||
30 | iLocalDef iBool isClosingBracket_(iChar c) { | ||
31 | return (c == ')' || c == ']' || c == '}' || c == '>'); | ||
32 | } | ||
33 | |||
34 | iLocalDef 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 | |||
55 | iLocalDef iBool isMeasuring_(enum iRunMode mode) { | ||
56 | return (mode & modeMask_RunMode) == measure_RunMode; | ||
57 | } | ||
58 | |||
25 | static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | 59 | static 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; |