diff options
author | Neil <nyamatongwe@gmail.com> | 2021-03-19 16:15:16 +1100 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2021-03-19 16:15:16 +1100 |
commit | 8b8a02b4bd250d28bc0f42287ca3d167b7210f39 (patch) | |
tree | d8d42a94f5abdaf1247db4fc5bf766c9a72cfbe9 | |
parent | 09534ab81f72ce766c8d13ea13216dd589d41a1c (diff) | |
download | scintilla-mirror-8b8a02b4bd250d28bc0f42287ca3d167b7210f39.tar.gz |
UTF-8 text drawing and measurement.
Move SurfaceGDI::WidthText to match declaration order.
-rw-r--r-- | cocoa/PlatCocoa.h | 10 | ||||
-rw-r--r-- | cocoa/PlatCocoa.mm | 98 | ||||
-rwxr-xr-x | gtk/PlatGTK.cxx | 88 | ||||
-rw-r--r-- | qt/ScintillaEditBase/PlatQt.cpp | 81 | ||||
-rw-r--r-- | qt/ScintillaEditBase/PlatQt.h | 11 | ||||
-rw-r--r-- | src/Platform.h | 7 | ||||
-rw-r--r-- | win32/PlatWin.cxx | 293 |
7 files changed, 554 insertions, 34 deletions
diff --git a/cocoa/PlatCocoa.h b/cocoa/PlatCocoa.h index 1ddb203c7..b918e5ed0 100644 --- a/cocoa/PlatCocoa.h +++ b/cocoa/PlatCocoa.h @@ -104,6 +104,7 @@ public: void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; void Copy(PRectangle rc, Scintilla::Point from, Surface &surfaceSource) override; std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; + void DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; void DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, @@ -111,6 +112,15 @@ public: void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override; XYPOSITION WidthText(const Font *font_, std::string_view text) override; + + void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, + ColourDesired back) override; + void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, + ColourDesired back) override; + void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; + void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override; + XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override; + XYPOSITION Ascent(const Font *font_) override; XYPOSITION Descent(const Font *font_) override; XYPOSITION InternalLeading(const Font *font_) override; diff --git a/cocoa/PlatCocoa.mm b/cocoa/PlatCocoa.mm index 2e3ab2d57..f84a335aa 100644 --- a/cocoa/PlatCocoa.mm +++ b/cocoa/PlatCocoa.mm @@ -1227,6 +1227,104 @@ XYPOSITION SurfaceImpl::WidthText(const Font *font_, std::string_view text) { return static_cast<XYPOSITION>(textLayout->MeasureStringWidth()); } +//-------------------------------------------------------------------------------------------------- + +void SurfaceImpl::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore, ColourDesired back) { + FillRectangle(rc, back); + DrawTextTransparent(rc, font_, ybase, text, fore); +} + +//-------------------------------------------------------------------------------------------------- + +void SurfaceImpl::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore, ColourDesired back) { + CGContextSaveGState(gc); + CGContextClipToRect(gc, PRectangleToCGRect(rc)); + DrawTextNoClip(rc, font_, ybase, text, fore, back); + CGContextRestoreGState(gc); +} + +//-------------------------------------------------------------------------------------------------- + +void SurfaceImpl::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore) { + QuartzTextStyle *style = TextStyleFromFont(font_); + if (!style) { + return; + } + const CFStringEncoding encoding = kCFStringEncodingUTF8; + ColourDesired colour(fore.AsInteger()); + CGColorRef color = CGColorCreateGenericRGB(colour.GetRed()/255.0, colour.GetGreen()/255.0, colour.GetBlue()/255.0, 1.0); + + style->setCTStyleColour(color); + + CGColorRelease(color); + + textLayout->setText(text, encoding, style); + textLayout->draw(gc, rc.left, ybase); +} + +//-------------------------------------------------------------------------------------------------- + +void SurfaceImpl::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) { + const QuartzTextStyle *style = TextStyleFromFont(font_); + if (!style) { + return; + } + const CFStringEncoding encoding = kCFStringEncodingUTF8; + const CFStringEncoding encodingUsed = + textLayout->setText(text, encoding, style); + + CTLineRef mLine = textLayout->getCTLine(); + assert(mLine); + + if (encodingUsed != encoding) { + // Switched to MacRoman to make work so treat as single byte encoding. + for (int i=0; i<text.length(); i++) { + CGFloat xPosition = CTLineGetOffsetForStringIndex(mLine, i+1, nullptr); + positions[i] = static_cast<XYPOSITION>(xPosition); + } + return; + } + + // Map the widths given for UTF-16 characters back onto the UTF-8 input string + CFIndex fit = textLayout->getStringLength(); + int ui=0; + int i=0; + std::vector<CGFloat> linePositions(fit); + GetPositions(mLine, linePositions); + while (ui<fit) { + const unsigned char uch = text[i]; + const unsigned int byteCount = UTF8BytesOfLead[uch]; + const int codeUnits = UTF16LengthFromUTF8ByteCount(byteCount); + const CGFloat xPosition = linePositions[ui]; + for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()); bytePos++) { + positions[i++] = static_cast<XYPOSITION>(xPosition); + } + ui += codeUnits; + } + XYPOSITION lastPos = 0.0f; + if (i > 0) + lastPos = positions[i-1]; + while (i<text.length()) { + positions[i++] = lastPos; + } +} + +XYPOSITION SurfaceImpl::WidthTextUTF8(const Font *font_, std::string_view text) { + const QuartzTextStyle *style = TextStyleFromFont(font_); + if (!style) { + return 1; + } + textLayout->setText(text, kCFStringEncodingUTF8, style); + + return static_cast<XYPOSITION>(textLayout->MeasureStringWidth()); +} + +//-------------------------------------------------------------------------------------------------- + + // This string contains a good range of characters to test for size. const char sizeString[] = "`~!@#$%^&*()-_=+\\|[]{};:\"\'<,>.?/1234567890" "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; diff --git a/gtk/PlatGTK.cxx b/gtk/PlatGTK.cxx index 3f0918886..b9c049f1d 100755 --- a/gtk/PlatGTK.cxx +++ b/gtk/PlatGTK.cxx @@ -180,6 +180,14 @@ public: void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override; XYPOSITION WidthText(const Font *font_, std::string_view text) override; + + void DrawTextBaseUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore); + void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; + void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; + void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; + void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override; + XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override; + XYPOSITION Ascent(const Font *font_) override; XYPOSITION Descent(const Font *font_) override; XYPOSITION InternalLeading(const Font *font_) override; @@ -914,6 +922,86 @@ XYPOSITION SurfaceImpl::WidthText(const Font *font_, std::string_view text) { return 1; } +void SurfaceImpl::DrawTextBaseUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore) { + PenColour(fore); + if (context) { + const XYPOSITION xText = rc.left; + if (PFont(font_)->pfd) { + pango_layout_set_text(layout, text.data(), text.length()); + pango_layout_set_font_description(layout, PFont(font_)->pfd); + pango_cairo_update_layout(context, layout); + PangoLayoutLine *pll = pango_layout_get_line_readonly(layout, 0); + cairo_move_to(context, xText, ybase); + pango_cairo_show_layout_line(context, pll); + } + } +} + +void SurfaceImpl::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore, ColourDesired back) { + FillRectangle(rc, back); + DrawTextBaseUTF8(rc, font_, ybase, text, fore); +} + +// On GTK+, exactly same as DrawTextNoClip +void SurfaceImpl::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore, ColourDesired back) { + FillRectangle(rc, back); + DrawTextBaseUTF8(rc, font_, ybase, text, fore); +} + +void SurfaceImpl::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore) { + // Avoid drawing spaces in transparent mode + for (size_t i = 0; i < text.length(); i++) { + if (text[i] != ' ') { + DrawTextBaseUTF8(rc, font_, ybase, text, fore); + return; + } + } +} + +void SurfaceImpl::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) { + if (PFont(font_)->pfd) { + pango_layout_set_font_description(layout, PFont(font_)->pfd); + // Simple and direct as UTF-8 is native Pango encoding + int i = 0; + pango_layout_set_text(layout, text.data(), text.length()); + ClusterIterator iti(layout, text.length()); + while (!iti.finished) { + iti.Next(); + const int places = iti.curIndex - i; + while (i < iti.curIndex) { + // Evenly distribute space among bytes of this cluster. + // Would be better to find number of characters and then + // divide evenly between characters with each byte of a character + // being at the same position. + positions[i] = iti.position - (iti.curIndex - 1 - i) * iti.distance / places; + i++; + } + } + PLATFORM_ASSERT(static_cast<size_t>(i) == text.length()); + } else { + // No font so return an ascending range of values + for (size_t i = 0; i < text.length(); i++) { + positions[i] = i + 1; + } + } +} + +XYPOSITION SurfaceImpl::WidthTextUTF8(const Font *font_, std::string_view text) { + if (PFont(font_)->pfd) { + pango_layout_set_font_description(layout, PFont(font_)->pfd); + pango_layout_set_text(layout, text.data(), text.length()); + PangoLayoutLine *pangoLine = pango_layout_get_line_readonly(layout, 0); + PangoRectangle pos{}; + pango_layout_line_get_extents(pangoLine, nullptr, &pos); + return floatFromPangoUnits(pos.width); + } + return 1; +} + // Ascent and descent determined by Pango font metrics. XYPOSITION SurfaceImpl::Ascent(const Font *font_) { diff --git a/qt/ScintillaEditBase/PlatQt.cpp b/qt/ScintillaEditBase/PlatQt.cpp index 6a26d47c4..a1adf0a0d 100644 --- a/qt/ScintillaEditBase/PlatQt.cpp +++ b/qt/ScintillaEditBase/PlatQt.cpp @@ -557,6 +557,87 @@ XYPOSITION SurfaceImpl::WidthText(const Font *font, std::string_view text) return metrics.width(su); } +void SurfaceImpl::DrawTextNoClipUTF8(PRectangle rc, + const Font *font, + XYPOSITION ybase, + std::string_view text, + ColourDesired fore, + ColourDesired back) +{ + SetFont(font); + PenColour(fore); + + GetPainter()->setBackground(QColorFromCA(back)); + GetPainter()->setBackgroundMode(Qt::OpaqueMode); + QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length())); + GetPainter()->drawText(QPointF(rc.left, ybase), su); +} + +void SurfaceImpl::DrawTextClippedUTF8(PRectangle rc, + const Font *font, + XYPOSITION ybase, + std::string_view text, + ColourDesired fore, + ColourDesired back) +{ + SetClip(rc); + DrawTextNoClip(rc, font, ybase, text, fore, back); + PopClip(); +} + +void SurfaceImpl::DrawTextTransparentUTF8(PRectangle rc, + const Font *font, + XYPOSITION ybase, + std::string_view text, + ColourDesired fore) +{ + SetFont(font); + PenColour(fore); + + GetPainter()->setBackgroundMode(Qt::TransparentMode); + QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length())); + GetPainter()->drawText(QPointF(rc.left, ybase), su); +} + +void SurfaceImpl::MeasureWidthsUTF8(const Font *font, + std::string_view text, + XYPOSITION *positions) +{ + if (!font) + return; + QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length())); + QTextLayout tlay(su, *FontPointer(font), GetPaintDevice()); + tlay.beginLayout(); + QTextLine tl = tlay.createLine(); + tlay.endLayout(); + int fit = su.size(); + int ui=0; + size_t i=0; + while (ui<fit) { + const unsigned char uch = text[i]; + const unsigned int byteCount = UTF8BytesOfLead[uch]; + const int codeUnits = UTF16LengthFromUTF8ByteCount(byteCount); + qreal xPosition = tl.cursorToX(ui+codeUnits); + for (size_t bytePos=0; (bytePos<byteCount) && (i<text.length()); bytePos++) { + positions[i++] = xPosition; + } + ui += codeUnits; + } + XYPOSITION lastPos = 0; + if (i > 0) + lastPos = positions[i-1]; + while (i<text.length()) { + positions[i++] = lastPos; + } +} + +XYPOSITION SurfaceImpl::WidthTextUTF8(const Font *font, std::string_view text) +{ + QFontMetricsF metrics(*FontPointer(font), device); + QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length())); + return metrics.width(su); +} + XYPOSITION SurfaceImpl::Ascent(const Font *font) { QFontMetricsF metrics(*FontPointer(font), device); diff --git a/qt/ScintillaEditBase/PlatQt.h b/qt/ScintillaEditBase/PlatQt.h index c690e7697..a743a4be5 100644 --- a/qt/ScintillaEditBase/PlatQt.h +++ b/qt/ScintillaEditBase/PlatQt.h @@ -126,6 +126,17 @@ public: void MeasureWidths(const Font *font, std::string_view text, XYPOSITION *positions) override; XYPOSITION WidthText(const Font *font, std::string_view text) override; + + void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, + std::string_view text, ColourDesired fore, ColourDesired back) override; + void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, + std::string_view text, ColourDesired fore, ColourDesired back) override; + void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, + std::string_view text, ColourDesired fore) override; + void MeasureWidthsUTF8(const Font *font_, std::string_view text, + XYPOSITION *positions) override; + XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override; + XYPOSITION Ascent(const Font *font) override; XYPOSITION Descent(const Font *font) override; XYPOSITION InternalLeading(const Font *font) override; diff --git a/src/Platform.h b/src/Platform.h index 3333d3d4c..45c04abdc 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -208,6 +208,13 @@ public: virtual void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) = 0; virtual void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) = 0; virtual XYPOSITION WidthText(const Font *font_, std::string_view text) = 0; + + virtual void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) = 0; + virtual void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) = 0; + virtual void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) = 0; + virtual void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) = 0; + virtual XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) = 0; + virtual XYPOSITION Ascent(const Font *font_)=0; virtual XYPOSITION Descent(const Font *font_)=0; virtual XYPOSITION InternalLeading(const Font *font_)=0; diff --git a/win32/PlatWin.cxx b/win32/PlatWin.cxx index 26a351871..3f93a96fd 100644 --- a/win32/PlatWin.cxx +++ b/win32/PlatWin.cxx @@ -506,6 +506,14 @@ public: void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override; XYPOSITION WidthText(const Font *font_, std::string_view text) override; + + void DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions); + void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; + void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; + void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; + void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override; + XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override; + XYPOSITION Ascent(const Font *font_) override; XYPOSITION Descent(const Font *font_) override; XYPOSITION InternalLeading(const Font *font_) override; @@ -1003,23 +1011,11 @@ void SurfaceGDI::DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITIO } } -XYPOSITION SurfaceGDI::WidthText(const Font *font_, std::string_view text) { - SetFont(font_); - SIZE sz={0,0}; - if (!unicodeMode) { - ::GetTextExtentPoint32A(hdc, text.data(), std::min(static_cast<int>(text.length()), maxLenText), &sz); - } else { - const TextWide tbuf(text, unicodeMode, codePage); - ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz); - } - return static_cast<XYPOSITION>(sz.cx); -} - void SurfaceGDI::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) { // Zero positions to avoid random behaviour on failure. std::fill(positions, positions + text.length(), 0.0f); SetFont(font_); - SIZE sz={0,0}; + SIZE sz = { 0,0 }; int fit = 0; int i = 0; const int len = static_cast<int>(text.length()); @@ -1037,7 +1033,7 @@ void SurfaceGDI::MeasureWidths(const Font *font_, std::string_view text, XYPOSIT if (byteCount == 4) { // Non-BMP ui++; } - for (unsigned int bytePos=0; (bytePos<byteCount) && (i<len); bytePos++) { + for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < len); bytePos++) { positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]); } } @@ -1047,14 +1043,102 @@ void SurfaceGDI::MeasureWidths(const Font *font_, std::string_view text, XYPOSIT // Eeek - a NULL DC or other foolishness could cause this. return; } - while (i<fit) { + while (i < fit) { positions[i] = static_cast<XYPOSITION>(poses.buffer[i]); i++; } } // If any positions not filled in then use the last position for them const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f; - std::fill(positions+i, positions + text.length(), lastPos); + std::fill(positions + i, positions + text.length(), lastPos); +} + +XYPOSITION SurfaceGDI::WidthText(const Font *font_, std::string_view text) { + SetFont(font_); + SIZE sz = { 0,0 }; + if (!unicodeMode) { + ::GetTextExtentPoint32A(hdc, text.data(), std::min(static_cast<int>(text.length()), maxLenText), &sz); + } else { + const TextWide tbuf(text, unicodeMode, codePage); + ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz); + } + return static_cast<XYPOSITION>(sz.cx); +} + +void SurfaceGDI::DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) { + SetFont(font_); + const RECT rcw = RectFromPRectangle(rc); + const int x = static_cast<int>(rc.left); + const int yBaseInt = static_cast<int>(ybase); + + const TextWide tbuf(text, true); + ::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr); +} + +void SurfaceGDI::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore, ColourDesired back) { + ::SetTextColor(hdc, fore.AsInteger()); + ::SetBkColor(hdc, back.AsInteger()); + DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE); +} + +void SurfaceGDI::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore, ColourDesired back) { + ::SetTextColor(hdc, fore.AsInteger()); + ::SetBkColor(hdc, back.AsInteger()); + DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED); +} + +void SurfaceGDI::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore) { + // Avoid drawing spaces in transparent mode + for (const char ch : text) { + if (ch != ' ') { + ::SetTextColor(hdc, fore.AsInteger()); + ::SetBkMode(hdc, TRANSPARENT); + DrawTextCommonUTF8(rc, font_, ybase, text, 0); + ::SetBkMode(hdc, OPAQUE); + return; + } + } +} + +void SurfaceGDI::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) { + // Zero positions to avoid random behaviour on failure. + std::fill(positions, positions + text.length(), 0.0f); + SetFont(font_); + SIZE sz = { 0,0 }; + int fit = 0; + int i = 0; + const int len = static_cast<int>(text.length()); + const TextWide tbuf(text, true); + TextPositionsI poses(tbuf.tlen); + if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) { + // Failure + return; + } + // Map the widths given for UTF-16 characters back onto the UTF-8 input string + for (int ui = 0; ui < fit; ui++) { + const unsigned char uch = text[i]; + const unsigned int byteCount = UTF8BytesOfLead[uch]; + if (byteCount == 4) { // Non-BMP + ui++; + } + for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < len); bytePos++) { + positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]); + } + } + // If any positions not filled in then use the last position for them + const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f; + std::fill(positions + i, positions + text.length(), lastPos); +} + +XYPOSITION SurfaceGDI::WidthTextUTF8(const Font *font_, std::string_view text) { + SetFont(font_); + SIZE sz = { 0,0 }; + const TextWide tbuf(text, true); + ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz); + return static_cast<XYPOSITION>(sz.cx); } XYPOSITION SurfaceGDI::Ascent(const Font *font_) { @@ -1208,6 +1292,14 @@ public: void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override; XYPOSITION WidthText(const Font *font_, std::string_view text) override; + + void DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions); + void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; + void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; + void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; + void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override; + XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override; + XYPOSITION Ascent(const Font *font_) override; XYPOSITION Descent(const Font *font_) override; XYPOSITION InternalLeading(const Font *font_) override; @@ -2104,24 +2196,6 @@ void SurfaceD2D::DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITIO } } -XYPOSITION SurfaceD2D::WidthText(const Font *font_, std::string_view text) { - FLOAT width = 1.0; - SetFont(font_); - const TextWide tbuf(text, unicodeMode, codePageText); - if (pIDWriteFactory && pTextFormat) { - // Create a layout - IDWriteTextLayout *pTextLayout = nullptr; - const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout); - if (SUCCEEDED(hr) && pTextLayout) { - DWRITE_TEXT_METRICS textMetrics; - if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) - width = textMetrics.widthIncludingTrailingWhitespace; - ReleaseUnknown(pTextLayout); - } - } - return width; -} - void SurfaceD2D::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) { SetFont(font_); if (!pIDWriteFactory || !pTextFormat) { @@ -2203,6 +2277,157 @@ void SurfaceD2D::MeasureWidths(const Font *font_, std::string_view text, XYPOSIT } } +XYPOSITION SurfaceD2D::WidthText(const Font *font_, std::string_view text) { + FLOAT width = 1.0; + SetFont(font_); + const TextWide tbuf(text, unicodeMode, codePageText); + if (pIDWriteFactory && pTextFormat) { + // Create a layout + IDWriteTextLayout *pTextLayout = nullptr; + const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout); + if (SUCCEEDED(hr) && pTextLayout) { + DWRITE_TEXT_METRICS textMetrics; + if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) + width = textMetrics.widthIncludingTrailingWhitespace; + ReleaseUnknown(pTextLayout); + } + } + return width; +} + +void SurfaceD2D::DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) { + SetFont(font_); + + // Use Unicode calls + const TextWide tbuf(text, true); + if (pRenderTarget && pTextFormat && pBrush) { + if (fuOptions & ETO_CLIPPED) { + D2D1_RECT_F rcClip = { rc.left, rc.top, rc.right, rc.bottom }; + pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED); + } + + // Explicitly creating a text layout appears a little faster + IDWriteTextLayout *pTextLayout = nullptr; + const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, + rc.Width(), rc.Height(), &pTextLayout); + if (SUCCEEDED(hr)) { + D2D1_POINT_2F origin = {rc.left, ybase-yAscent}; + pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, d2dDrawTextOptions); + ReleaseUnknown(pTextLayout); + } + + if (fuOptions & ETO_CLIPPED) { + pRenderTarget->PopAxisAlignedClip(); + } + } +} + +void SurfaceD2D::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore, ColourDesired back) { + if (pRenderTarget) { + FillRectangle(rc, back); + D2DPenColour(fore); + DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE); + } +} + +void SurfaceD2D::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore, ColourDesired back) { + if (pRenderTarget) { + FillRectangle(rc, back); + D2DPenColour(fore); + DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED); + } +} + +void SurfaceD2D::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourDesired fore) { + // Avoid drawing spaces in transparent mode + for (const char ch : text) { + if (ch != ' ') { + if (pRenderTarget) { + D2DPenColour(fore); + DrawTextCommonUTF8(rc, font_, ybase, text, 0); + } + return; + } + } +} + +void SurfaceD2D::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) { + SetFont(font_); + if (!pIDWriteFactory || !pTextFormat) { + // SetFont failed or no access to DirectWrite so give up. + return; + } + const TextWide tbuf(text, true); + TextPositions poses(tbuf.tlen); + // Initialize poses for safety. + std::fill(poses.buffer, poses.buffer + tbuf.tlen, 0.0f); + // Create a layout + IDWriteTextLayout *pTextLayout = nullptr; + const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout); + if (!SUCCEEDED(hrCreate)) { + return; + } + constexpr int clusters = stackBufferLength; + DWRITE_CLUSTER_METRICS clusterMetrics[clusters]; + UINT32 count = 0; + const HRESULT hrGetCluster = pTextLayout->GetClusterMetrics(clusterMetrics, clusters, &count); + ReleaseUnknown(pTextLayout); + if (!SUCCEEDED(hrGetCluster)) { + return; + } + // A cluster may be more than one WCHAR, such as for "ffi" which is a ligature in the Candara font + FLOAT position = 0.0f; + size_t ti = 0; + for (size_t ci = 0; ci < count; ci++) { + for (size_t inCluster = 0; inCluster < clusterMetrics[ci].length; inCluster++) { + poses.buffer[ti++] = position + clusterMetrics[ci].width * (inCluster + 1) / clusterMetrics[ci].length; + } + position += clusterMetrics[ci].width; + } + PLATFORM_ASSERT(ti == static_cast<size_t>(tbuf.tlen)); + // Map the widths given for UTF-16 characters back onto the UTF-8 input string + int ui = 0; + size_t i = 0; + while (ui < tbuf.tlen) { + const unsigned char uch = text[i]; + const unsigned int byteCount = UTF8BytesOfLead[uch]; + if (byteCount == 4) { // Non-BMP + ui++; + } + for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < text.length()); bytePos++) { + positions[i++] = poses.buffer[ui]; + } + ui++; + } + XYPOSITION lastPos = 0.0f; + if (i > 0) + lastPos = positions[i - 1]; + while (i < text.length()) { + positions[i++] = lastPos; + } +} + +XYPOSITION SurfaceD2D::WidthTextUTF8(const Font * font_, std::string_view text) { + FLOAT width = 1.0; + SetFont(font_); + const TextWide tbuf(text, true); + if (pIDWriteFactory && pTextFormat) { + // Create a layout + IDWriteTextLayout *pTextLayout = nullptr; + const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout); + if (SUCCEEDED(hr)) { + DWRITE_TEXT_METRICS textMetrics; + if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) + width = textMetrics.widthIncludingTrailingWhitespace; + ReleaseUnknown(pTextLayout); + } + } + return width; +} + XYPOSITION SurfaceD2D::Ascent(const Font *font_) { SetFont(font_); return std::ceil(yAscent); |