diff --git a/GpApp/HouseInfo.cpp b/GpApp/HouseInfo.cpp index 15b6ee5..53da7af 100644 --- a/GpApp/HouseInfo.cpp +++ b/GpApp/HouseInfo.cpp @@ -14,6 +14,8 @@ #include "DialogUtils.h" #include "HostDisplayDriver.h" #include "IGpDisplayDriver.h" +#include "PLArrayView.h" +#include "PLEditboxWidget.h" #include "PLTimeTaggedVOSEvent.h" @@ -231,7 +233,10 @@ void DoHouseInfo (void) RedAlert(kErrDialogDidntLoad); SetPort(&houseInfoDialog->GetWindow()->GetDrawSurface()->m_port); ShowWindow(houseInfoDialog->GetWindow()); - + + static_cast(houseInfoDialog->GetItems()[kBannerTextItem - 1].GetWidget())->SetMultiLine(true); + static_cast(houseInfoDialog->GetItems()[kTrailerTextItem - 1].GetWidget())->SetMultiLine(true); + SetDialogString(houseInfoDialog, kBannerTextItem, banner); SetDialogString(houseInfoDialog, kTrailerTextItem, trailer); SelectDialogItemText(houseInfoDialog, kBannerTextItem, 0, 1024); diff --git a/PortabilityLayer/PLEditboxWidget.cpp b/PortabilityLayer/PLEditboxWidget.cpp index 844e574..1cfa96b 100644 --- a/PortabilityLayer/PLEditboxWidget.cpp +++ b/PortabilityLayer/PLEditboxWidget.cpp @@ -1,12 +1,16 @@ #include "PLEditboxWidget.h" #include "FontFamily.h" +#include "FontManager.h" #include "InputManager.h" #include "MacRomanConversion.h" #include "MemoryManager.h" +#include "RenderedFont.h" +#include "RenderedFontMetrics.h" #include "PLKeyEncoding.h" #include "PLStandardColors.h" #include "PLTimeTaggedVOSEvent.h" +#include "TextPlacer.h" #include @@ -19,8 +23,12 @@ namespace PortabilityLayer , m_chars(nullptr) , m_selStartChar(0) , m_selEndChar(0) + , m_caratSelectionAnchor(CaratSelectionAnchor_End) + , m_caratScrollPosition(0, 0) + , m_caratScrollLocked(false) , m_hasFocus(false) , m_caratTimer(0) + , m_isMultiLine(false) { } @@ -70,45 +78,29 @@ namespace PortabilityLayer const char *strChars = str.Chars(); - size_t preSelWidth = 0; - size_t selWidth = 0; - //size_t postSelWidth = 0; - if (m_selStartChar > 0) - preSelWidth = surface->MeasureString(PLPasStr(static_cast(m_selStartChar), strChars)); + Vec2i basePoint = ResolveBasePoint(); - if (m_selEndChar > m_selStartChar) - selWidth = surface->MeasureString(PLPasStr(static_cast(m_selEndChar - m_selStartChar), strChars + m_selStartChar)); + if (m_hasFocus && m_selStartChar != m_selEndChar) + DrawSelection(surface, basePoint); - //if (m_selEndChar < str.Length()) - // postSelWidth = surface->MeasureString(PLPasStr(static_cast(m_selEndChar - str.Length()), strChars + m_selEndChar)); - - Point basePoint = Point::Create(textRect.left, (textRect.top + textRect.bottom + ascender + 1) / 2); - - if (m_hasFocus && selWidth > 0) - { - Rect selRect = Rect::Create(m_rect.top, static_cast(basePoint.h + preSelWidth), m_rect.bottom, m_rect.right); - if (m_selEndChar != str.Length()) - selRect.right = static_cast(basePoint.h + preSelWidth + selWidth); - - selRect = selRect.Intersect(m_rect); - - if (selRect.IsValid()) - { - PortabilityLayer::RGBAColor focusColor = PortabilityLayer::RGBAColor::Create(153, 153, 255, 255); - surface->SetForeColor(focusColor); - surface->FillRect(selRect); - } - } + int32_t verticalOffset = (ascender + lineGap + 1) / 2; surface->SetForeColor(StdColors::Black()); - surface->DrawStringConstrained(basePoint, this->GetString(), true, m_rect); + + const Point stringBasePoint = Point::Create(basePoint.m_x, basePoint.m_y + verticalOffset); + + if (m_isMultiLine) + surface->DrawStringWrap(stringBasePoint, m_rect, this->GetString(), true); + else + surface->DrawStringConstrained(stringBasePoint, this->GetString(), true, m_rect); if (m_hasFocus && m_selEndChar == m_selStartChar && m_caratTimer < kCaratBlinkRate) { - int16_t caratTop = (textRect.top + textRect.bottom - lineGap + 1) / 2; - int16_t caratBottom = (textRect.top + textRect.bottom + lineGap + 1) / 2; - int16_t caratH = static_cast(basePoint.h + preSelWidth); - Rect caratRect = Rect::Create(caratTop, caratH, caratBottom, caratH + 1); + PortabilityLayer::Vec2i caratPos = ResolveCaratPos(basePoint, surface->ResolveFont(true)); + + int32_t caratTop = caratPos.m_y; + int32_t caratBottom = caratTop + lineGap; + Rect caratRect = Rect::Create(caratTop, caratPos.m_x, caratBottom, caratPos.m_x + 1); caratRect = caratRect.Intersect(m_rect); @@ -148,6 +140,8 @@ namespace PortabilityLayer m_hasFocus = true; m_selStartChar = 0; m_selEndChar = this->GetString().Length(); + m_caratSelectionAnchor = CaratSelectionAnchor_End; + m_caratScrollLocked = false; if (m_window) { @@ -161,6 +155,8 @@ namespace PortabilityLayer m_hasFocus = false; m_selStartChar = 0; m_selEndChar = 0; + m_caratSelectionAnchor = CaratSelectionAnchor_End; + m_caratScrollLocked = false; Redraw(); } @@ -200,6 +196,8 @@ namespace PortabilityLayer { if (ch >= 0x20 && ch <= 0x7e) HandleCharacter(ch, keyEvent.m_repeatCount); + else if ((ch == '\r' || ch == '\n') && m_isMultiLine) + HandleCharacter('\r', keyEvent.m_repeatCount); return WidgetHandleStates::kDigested; } @@ -216,13 +214,38 @@ namespace PortabilityLayer HandleBackspace(keyEvent.m_repeatCount); return WidgetHandleStates::kDigested; } + else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kUpArrow) + { + HandleUpArrow(keyEvent.m_repeatCount, isShiftHeld); + return WidgetHandleStates::kDigested; + } + else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kLeftArrow) + { + HandleLeftArrow(keyEvent.m_repeatCount, isShiftHeld); + return WidgetHandleStates::kDigested; + } + else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kRightArrow) + { + HandleRightArrow(keyEvent.m_repeatCount, isShiftHeld); + return WidgetHandleStates::kDigested; + } + else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kDownArrow) + { + HandleDownArrow(keyEvent.m_repeatCount, isShiftHeld); + return WidgetHandleStates::kDigested; + } +#if 0 else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kDelete) { return WidgetHandleStates::kDigested; } +#endif } } } + else if (evt.m_vosEvent.m_eventType == GpVOSEventTypes::kMouseInput) + { + } return WidgetHandleStates::kIgnored; } @@ -259,6 +282,7 @@ namespace PortabilityLayer m_selStartChar = startChar; m_selEndChar = endChar; + m_caratSelectionAnchor = CaratSelectionAnchor_End; m_caratTimer = 0; Redraw(); @@ -313,6 +337,8 @@ namespace PortabilityLayer m_caratTimer = 0; Redraw(); + + m_caratScrollLocked = false; } void EditboxWidget::HandleBackspace(uint32_t numRepeatsRequested) @@ -345,5 +371,369 @@ namespace PortabilityLayer m_caratTimer = 0; Redraw(); + + m_caratScrollLocked = false; + } + + void EditboxWidget::HandleUpArrow(const uint32_t numRepeatsRequested, bool shiftHeld) + { + if (!m_isMultiLine) + return; + + size_t caratChar = ResolveCaratChar(); + + PortabilityLayer::RenderedFont *rfont = GetRenderedFont(); + int32_t lineGap = rfont->GetMetrics().m_linegap; + + if (!rfont) + return; + + if (!m_caratScrollLocked) + { + m_caratScrollPosition = ResolveCaratPos(Vec2i(0, 0), rfont); + m_caratScrollLocked = true; + } + + Vec2i caratPos = m_caratScrollPosition; + + for (uint32_t r = 0; r < numRepeatsRequested; r++) + { + bool isOutOfRange = false; + m_caratScrollPosition.m_y -= lineGap; + caratChar = FindVerticalMovementCaratPos(m_caratScrollPosition, isOutOfRange); + HandleKeyMoveCarat(caratChar, shiftHeld); + + if (isOutOfRange) + { + m_caratScrollPosition.m_y += lineGap; + break; + } + } + + m_caratTimer = 0; + Redraw(); + } + + void EditboxWidget::HandleDownArrow(const uint32_t numRepeatsRequested, bool shiftHeld) + { + if (!m_isMultiLine) + return; + + size_t caratChar = ResolveCaratChar(); + + PortabilityLayer::RenderedFont *rfont = GetRenderedFont(); + int32_t lineGap = rfont->GetMetrics().m_linegap; + + if (!rfont) + return; + + if (!m_caratScrollLocked) + { + m_caratScrollPosition = ResolveCaratPos(Vec2i(0, 0), rfont); + m_caratScrollLocked = true; + } + + Vec2i caratPos = m_caratScrollPosition; + + for (uint32_t r = 0; r < numRepeatsRequested; r++) + { + bool isOutOfRange = false; + m_caratScrollPosition.m_y += lineGap; + caratChar = FindVerticalMovementCaratPos(m_caratScrollPosition, isOutOfRange); + HandleKeyMoveCarat(caratChar, shiftHeld); + + if (isOutOfRange) + { + m_caratScrollPosition.m_y -= lineGap; + break; + } + } + + m_caratTimer = 0; + Redraw(); + } + + void EditboxWidget::HandleLeftArrow(const uint32_t numRepeatsRequested, bool shiftHeld) + { + size_t caratChar = ResolveCaratChar(); + + for (uint32_t r = 0; r < numRepeatsRequested; r++) + { + if (!shiftHeld && m_selStartChar != m_selEndChar) + m_selEndChar = m_selStartChar; + else if (caratChar > 0) + HandleKeyMoveCarat(caratChar - 1, shiftHeld); + } + + m_caratScrollLocked = false; + + m_caratTimer = 0; + Redraw(); + } + + void EditboxWidget::HandleRightArrow(const uint32_t numRepeatsRequested, bool shiftHeld) + { + size_t caratChar = ResolveCaratChar(); + + for (uint32_t r = 0; r < numRepeatsRequested; r++) + { + if (!shiftHeld && m_selStartChar != m_selEndChar) + m_selStartChar = m_selEndChar; + else if (caratChar < m_length) + HandleKeyMoveCarat(caratChar + 1, shiftHeld); + } + + m_caratScrollLocked = false; + + m_caratTimer = 0; + Redraw(); + } + + size_t EditboxWidget::FindVerticalMovementCaratPos(const Vec2i &desiredPos, bool &outShouldUnlock) const + { + assert(m_isMultiLine); + + Vec2i basePoint = Vec2i(0, 0); + + if (desiredPos.m_y < basePoint.m_y) + { + outShouldUnlock = true; + return 0; + } + + PortabilityLayer::TextPlacer placer(basePoint, m_rect.Width(), GetRenderedFont(), GetString()); + + bool foundLine = false; + size_t caratChar = 0; + + PortabilityLayer::GlyphPlacementCharacteristics characteristics; + while (placer.PlaceGlyph(characteristics)) + { + if (characteristics.m_glyphStartPos.m_y > desiredPos.m_y) + break; + + if (characteristics.m_glyphStartPos.m_y == desiredPos.m_y) + { + caratChar = characteristics.m_characterIndex; + if (characteristics.m_character == '\r') + caratChar--; + foundLine = true; + + if (characteristics.m_glyphStartPos.m_x <= desiredPos.m_x && characteristics.m_glyphEndPos.m_x > desiredPos.m_x) + { + int32_t distanceToEnd = characteristics.m_glyphEndPos.m_x - desiredPos.m_x; + int32_t distanceToStart = desiredPos.m_x - characteristics.m_glyphStartPos.m_x; + + if (distanceToStart <= distanceToEnd) + caratChar = characteristics.m_characterIndex; + else + caratChar = characteristics.m_characterIndex + 1; + + break; + } + } + } + + if (foundLine) + { + outShouldUnlock = false; + return caratChar; + } + + outShouldUnlock = true; + return m_length; + } + + void EditboxWidget::HandleKeyMoveCarat(size_t newPos, bool shiftHeld) + { + if (shiftHeld) + { + size_t otherSelection = m_selStartChar; + if (m_caratSelectionAnchor == CaratSelectionAnchor_Start) + otherSelection = m_selEndChar; + + m_selStartChar = std::min(newPos, otherSelection); + m_selEndChar = std::max(newPos, otherSelection); + + if (m_selStartChar == newPos) + m_caratSelectionAnchor = CaratSelectionAnchor_Start; + else if (m_selEndChar == newPos) + m_caratSelectionAnchor = CaratSelectionAnchor_End; + } + else + { + m_selStartChar = newPos; + m_selEndChar = newPos; + } + } + + void EditboxWidget::DrawSelection(DrawSurface *surface, const Vec2i &basePoint) const + { + PortabilityLayer::RenderedFont *rfont = surface->ResolveFont(true); + PortabilityLayer::TextPlacer placer(basePoint, m_isMultiLine ? m_rect.Width() : -1, rfont, GetString()); + +#if 0 + if (m_selStartChar == m_selEndChar) + { + PortabilityLayer::GlyphPlacementCharacteristics characteristics; + + for (;;) + { + const bool placedGlyph = placer.PlaceGlyph(characteristics); + + if (!placedGlyph) + break; + + if (characteristics.m_characterIndex == m_selStartChar) + { + caratPos = characteristics.m_glyphStartPos; + break; + } + else if (characteristics.m_characterIndex < m_selStartChar) + caratPos = characteristics.m_glyphEndPos; + else + break; + } + + outCaratPos = Point::Create(caratPos.m_x, caratPos.m_y); + return; + } +#endif + + PortabilityLayer::Vec2i globalSelStart; + PortabilityLayer::Vec2i globalSelEnd; + bool endIsLineBreak = false; + bool startSet = false; + bool endSet = false; + + PortabilityLayer::GlyphPlacementCharacteristics characteristics; + size_t placedIndex = 0; + + while (placer.PlaceGlyph(characteristics)) + { + bool isTerminalForThisPara = false; + bool isTerminalForEverything = false; + bool isLineBreakSelected = false; + + if (characteristics.m_characterIndex == m_selStartChar) + { + globalSelStart = characteristics.m_glyphStartPos; + startSet = true; + } + + if (characteristics.m_characterIndex + 1 == m_selEndChar) + { + globalSelEnd = characteristics.m_glyphEndPos; + if (characteristics.m_character == '\r') + endIsLineBreak = true; + + endSet = true; + break; + } + } + + if (!endSet || !startSet) + { + assert(false); + return; + } + + PortabilityLayer::RGBAColor focusColor = PortabilityLayer::RGBAColor::Create(153, 153, 255, 255); + surface->SetForeColor(focusColor); + + int32_t lineGap = rfont->GetMetrics().m_linegap; + int32_t ascender = rfont->GetMetrics().m_ascent; + int32_t startY = basePoint.m_y; + + if (globalSelStart.m_y == globalSelEnd.m_y) + { + Rect selRect = Rect::Create(globalSelStart.m_y, globalSelStart.m_x, globalSelStart.m_y + lineGap, globalSelEnd.m_x).Intersect(m_rect); + if (endIsLineBreak || (m_isMultiLine == false && m_selEndChar == m_length)) + selRect.right = m_rect.right; + + surface->FillRect(selRect); + } + else + { + const Rect firstLineRect = Rect::Create(globalSelStart.m_y, globalSelStart.m_x, globalSelStart.m_y + lineGap, m_rect.right).Intersect(m_rect); + surface->FillRect(firstLineRect); + + const Rect midLinesRect = Rect::Create(globalSelStart.m_y + lineGap, m_rect.left, globalSelEnd.m_y, m_rect.right).Intersect(m_rect); + surface->FillRect(midLinesRect); + + Rect lastLineRect = Rect::Create(globalSelEnd.m_y, m_rect.left, globalSelEnd.m_y + lineGap, globalSelEnd.m_x); + if (endIsLineBreak || (m_isMultiLine == false && m_selEndChar == m_length)) + lastLineRect.right = m_rect.right; + + surface->FillRect(lastLineRect); + } + } + + Vec2i EditboxWidget::ResolveCaratPos(const Vec2i &basePoint, PortabilityLayer::RenderedFont *rfont) const + { + int32_t lineGap = rfont->GetMetrics().m_linegap; + bool failed = false; + + PortabilityLayer::Vec2i caratPos = basePoint; + + const size_t caratChar = ResolveCaratChar(); + + if (caratChar > 0) + { + PortabilityLayer::RenderedFont *rfont = GetRenderedFont(); + PortabilityLayer::TextPlacer placer(basePoint, m_isMultiLine ? m_rect.Width() : -1, rfont, GetString()); + + PortabilityLayer::GlyphPlacementCharacteristics characteristics; + for (size_t i = 0; i < caratChar; i++) + { + if (!placer.PlaceGlyph(characteristics)) + { + failed = true; + break; + } + } + + if (!failed) + { + if (characteristics.m_character == '\r') + caratPos = PortabilityLayer::Vec2i(basePoint.m_x, characteristics.m_glyphStartPos.m_y + lineGap); + else + caratPos = characteristics.m_glyphEndPos; + } + } + + return caratPos; + } + + Vec2i EditboxWidget::ResolveBasePoint() const + { + return Vec2i(m_rect.left, m_rect.top); + } + + size_t EditboxWidget::ResolveCaratChar() const + { + if (m_caratSelectionAnchor == CaratSelectionAnchor_End) + return m_selEndChar; + else + return m_selStartChar; + } + + FontFamily *EditboxWidget::GetFontFamily() const + { + return PortabilityLayer::FontManager::GetInstance()->GetSystemFont(12, FontFamilyFlag_None); + } + + RenderedFont *EditboxWidget::GetRenderedFont() const + { + return PortabilityLayer::FontManager::GetInstance()->GetRenderedFontFromFamily(GetFontFamily(), 12, true, FontFamilyFlag_None); + } + + void EditboxWidget::SetMultiLine(bool isMultiLine) + { + if (m_isMultiLine != isMultiLine) + { + m_isMultiLine = isMultiLine; + Redraw(); + } } } diff --git a/PortabilityLayer/PLEditboxWidget.h b/PortabilityLayer/PLEditboxWidget.h index 4abd857..cc2ffcd 100644 --- a/PortabilityLayer/PLEditboxWidget.h +++ b/PortabilityLayer/PLEditboxWidget.h @@ -2,6 +2,9 @@ #include "PascalStr.h" #include "PLWidgets.h" +#include "Vec2i.h" + +struct DrawSurface; namespace PortabilityLayer { @@ -28,9 +31,17 @@ namespace PortabilityLayer void SetSelection(size_t startChar, size_t endChar); + void SetMultiLine(bool isMultiLine); + private: static const unsigned int kCaratBlinkRate = 20; + enum CaratSelectionAnchor + { + CaratSelectionAnchor_Start, + CaratSelectionAnchor_End + }; + void OnTick() override; void Redraw(); @@ -38,13 +49,35 @@ namespace PortabilityLayer void HandleBackspace(const uint32_t numRepeatsRequested); void HandleForwardDelete(const uint32_t numRepeatsRequested); + void HandleUpArrow(const uint32_t numRepeatsRequested, bool shiftHeld); + void HandleDownArrow(const uint32_t numRepeatsRequested, bool shiftHeld); + void HandleLeftArrow(const uint32_t numRepeatsRequested, bool shiftHeld); + void HandleRightArrow(const uint32_t numRepeatsRequested, bool shiftHeld); + + size_t FindVerticalMovementCaratPos(const Vec2i &desiredPos, bool &isOutOfRange) const; + void HandleKeyMoveCarat(size_t newPos, bool shiftHeld); + + void DrawSelection(DrawSurface *surface, const Vec2i &basePoint) const; + + Vec2i ResolveCaratPos(const Vec2i &basePoint, PortabilityLayer::RenderedFont *rfont) const; + Vec2i ResolveBasePoint() const; + size_t ResolveCaratChar() const; + + PortabilityLayer::FontFamily *GetFontFamily() const; + PortabilityLayer::RenderedFont *GetRenderedFont() const; + uint8_t *m_chars; size_t m_capacity; size_t m_length; size_t m_selStartChar; size_t m_selEndChar; + CaratSelectionAnchor m_caratSelectionAnchor; + + Vec2i m_caratScrollPosition; + bool m_caratScrollLocked; bool m_hasFocus; + bool m_isMultiLine; uint16_t m_caratTimer; }; } diff --git a/PortabilityLayer/PLQDraw.cpp b/PortabilityLayer/PLQDraw.cpp index 3f54c90..fb6b866 100644 --- a/PortabilityLayer/PLQDraw.cpp +++ b/PortabilityLayer/PLQDraw.cpp @@ -24,6 +24,7 @@ #include "ScanlineMaskIterator.h" #include "QDGraf.h" #include "QDStandardPalette.h" +#include "TextPlacer.h" #include "WindowManager.h" #include "QDGraf.h" #include "QDPixMap.h" @@ -288,7 +289,7 @@ void GetForeColor(RGBColor *color) *color = RGBColor(foreColor.r, foreColor.g, foreColor.b); } -static void DrawGlyph(PortabilityLayer::QDState *qdState, PixMap *pixMap, const Rect &rect, Point &penPos, const PortabilityLayer::RenderedFont *rfont, unsigned int character, +static void DrawGlyph(PortabilityLayer::QDState *qdState, PixMap *pixMap, const Rect &rect, const Point &penPos, const PortabilityLayer::RenderedFont *rfont, unsigned int character, PortabilityLayer::AntiAliasTable *&cachedAATable, PortabilityLayer::RGBAColor &cachedAATableColor) { assert(rect.IsValid()); @@ -298,12 +299,8 @@ static void DrawGlyph(PortabilityLayer::QDState *qdState, PixMap *pixMap, const if (!rfont->GetGlyph(character, metrics, data)) return; - const Point originalPoint = penPos; - - penPos.h += metrics->m_advanceX; - - const int32_t leftCoord = originalPoint.h + metrics->m_bearingX; - const int32_t topCoord = originalPoint.v - metrics->m_bearingY; + const int32_t leftCoord = penPos.h + metrics->m_bearingX; + const int32_t topCoord = penPos.v - metrics->m_bearingY; const int32_t rightCoord = leftCoord + metrics->m_glyphWidth; const int32_t bottomCoord = topCoord + metrics->m_glyphHeight; @@ -399,6 +396,17 @@ static void DrawGlyph(PortabilityLayer::QDState *qdState, PixMap *pixMap, const } } +static void DrawText(PortabilityLayer::TextPlacer &placer, PortabilityLayer::QDState *qdState, PixMap *pixMap, const Rect &rect, const PortabilityLayer::RenderedFont *rfont, + PortabilityLayer::AntiAliasTable *&cachedAATable, PortabilityLayer::RGBAColor &cachedAATableColor) +{ + PortabilityLayer::GlyphPlacementCharacteristics characteristics; + while (placer.PlaceGlyph(characteristics)) + { + if (characteristics.m_haveGlyph) + DrawGlyph(qdState, pixMap, rect, Point::Create(characteristics.m_glyphStartPos.m_x, characteristics.m_glyphStartPos.m_y), rfont, characteristics.m_character, cachedAATable, cachedAATableColor); + } +} + void DrawSurface::DrawString(const Point &point, const PLPasStr &str, bool aa) { DrawStringConstrained(point, str, aa, Rect::CreateLargest()); @@ -417,11 +425,6 @@ void DrawSurface::DrawStringConstrained(const Point &point, const PLPasStr &str, PortabilityLayer::FontFamily *fontFamily = qdState->m_fontFamily; PortabilityLayer::RenderedFont *rfont = fontManager->GetRenderedFontFromFamily(fontFamily, fontSize, aa, fontVariationFlags); - - Point penPos = point; - const size_t len = str.Length(); - const uint8_t *chars = str.UChars(); - PixMap *pixMap = *port->GetPixMap(); const Rect rect = pixMap->m_rect.Intersect(constraintRect); @@ -429,18 +432,9 @@ void DrawSurface::DrawStringConstrained(const Point &point, const PLPasStr &str, if (!rect.IsValid()) return; - Point paraStartPos = penPos; + PortabilityLayer::TextPlacer placer(PortabilityLayer::Vec2i(point.h, point.v), -1, rfont, str); - for (size_t i = 0; i < len; i++) - { - if (chars[i] == static_cast('\r')) - { - paraStartPos.v += rfont->GetMetrics().m_linegap; - penPos = paraStartPos; - } - else - DrawGlyph(qdState, pixMap, rect, penPos, rfont, chars[i], m_cachedAATable, m_cachedAAColor); - } + DrawText(placer, qdState, pixMap, rect, rfont, m_cachedAATable, m_cachedAAColor); m_port.SetDirty(PortabilityLayer::QDPortDirtyFlag_Contents); } @@ -471,91 +465,9 @@ void DrawSurface::DrawStringWrap(const Point &point, const Rect &constrainRect, if (!limitRect.IsValid() || !areaRect.IsValid()) return; // ??? - Point paraStartPos = penPos; + PortabilityLayer::TextPlacer placer(PortabilityLayer::Vec2i(point.h, point.v), areaRect.Width(), rfont, str); - size_t currentStartChar = 0; - size_t currentSpanLength = 0; - - for (;;) - { - if (currentStartChar == len) - break; - - size_t lastWhitespace = currentStartChar; - bool shouldSkipSpaces = false; - - size_t committedLength = 0; - - size_t i = currentStartChar; - - // Find a span to print - int32_t spanWidth = 0; - for (;;) - { - if (i == len) - { - committedLength = i - currentStartChar; - break; - } - - uint8_t character = chars[i]; - - if (character <= ' ') - { - committedLength = i - currentStartChar + 1; - if (character == '\r') - break; - - const PortabilityLayer::RenderedGlyphMetrics *metrics = nullptr; - const void *glyphData = nullptr; - if (rfont->GetGlyph(chars[i], metrics, glyphData)) - spanWidth += metrics->m_advanceX; - - i++; - } - else - { - const PortabilityLayer::RenderedGlyphMetrics *metrics = nullptr; - const void *glyphData = nullptr; - if (!rfont->GetGlyph(chars[i], metrics, glyphData)) - { - i++; - continue; - } - - spanWidth += metrics->m_advanceX; - - int32_t glyphEnd = penPos.h + spanWidth; - - if (glyphEnd >= constrainRect.right) - { - if (committedLength == 0) - { - // Word didn't fit - committedLength = i - currentStartChar; - if (committedLength == 0) - committedLength = 1; // Nothing fit, consume one char - } - - break; - } - - i++; - } - } - - for (size_t ci = 0; ci < committedLength; ci++) - { - const uint8_t character = chars[currentStartChar + ci]; - - DrawGlyph(qdState, pixMap, limitRect, penPos, rfont, character, m_cachedAATable, m_cachedAAColor); - } - - currentStartChar += committedLength; - - paraStartPos.v += rfont->GetMetrics().m_linegap; - penPos = paraStartPos; - } + DrawText(placer, qdState, pixMap, limitRect, rfont, m_cachedAATable, m_cachedAAColor); m_port.SetDirty(PortabilityLayer::QDPortDirtyFlag_Contents); } @@ -938,6 +850,19 @@ void DrawSurface::SetClipRect(const Rect &rect) m_port.GetState()->m_clipRect = rect;; } +PortabilityLayer::RenderedFont *DrawSurface::ResolveFont(bool aa) const +{ + const PortabilityLayer::QDState *qdState = m_port.GetState(); + + PortabilityLayer::FontManager *fontManager = PortabilityLayer::FontManager::GetInstance(); + + const int fontSize = qdState->m_fontSize; + const int fontVariationFlags = qdState->m_fontVariationFlags; + PortabilityLayer::FontFamily *fontFamily = qdState->m_fontFamily; + + return fontManager->GetRenderedFontFromFamily(fontFamily, fontSize, aa, fontVariationFlags); +} + void DrawSurface::FillRect(const Rect &rect) { if (!rect.IsValid()) diff --git a/PortabilityLayer/PortabilityLayer.vcxproj b/PortabilityLayer/PortabilityLayer.vcxproj index b8c7e99..928973e 100644 --- a/PortabilityLayer/PortabilityLayer.vcxproj +++ b/PortabilityLayer/PortabilityLayer.vcxproj @@ -228,6 +228,7 @@ + @@ -383,6 +384,7 @@ + diff --git a/PortabilityLayer/PortabilityLayer.vcxproj.filters b/PortabilityLayer/PortabilityLayer.vcxproj.filters index f426e2b..413da54 100644 --- a/PortabilityLayer/PortabilityLayer.vcxproj.filters +++ b/PortabilityLayer/PortabilityLayer.vcxproj.filters @@ -477,6 +477,9 @@ Header Files + + Header Files + @@ -749,5 +752,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/PortabilityLayer/QDGraf.h b/PortabilityLayer/QDGraf.h index b61f069..98bf22f 100644 --- a/PortabilityLayer/QDGraf.h +++ b/PortabilityLayer/QDGraf.h @@ -12,6 +12,7 @@ namespace PortabilityLayer struct AntiAliasTable; class FontFamily; struct RGBAColor; + class RenderedFont; class ScanlineMask; } @@ -95,6 +96,8 @@ struct DrawSurface final Rect GetClipRect() const; void SetClipRect(const Rect &rect); + PortabilityLayer::RenderedFont *ResolveFont(bool aa) const; + // Must be the first item PortabilityLayer::QDPort m_port; diff --git a/PortabilityLayer/TextPlacer.cpp b/PortabilityLayer/TextPlacer.cpp new file mode 100644 index 0000000..941ce3d --- /dev/null +++ b/PortabilityLayer/TextPlacer.cpp @@ -0,0 +1,151 @@ +#include "TextPlacer.h" + +#include "PLPasStr.h" +#include "RenderedFontMetrics.h" +#include "RenderedGlyphMetrics.h" +#include "RenderedFont.h" + +#include + +namespace PortabilityLayer +{ + TextPlacer::TextPlacer(const Vec2i &basePoint, int32_t spanWidth, PortabilityLayer::RenderedFont *rfont, const PLPasStr &str) + : m_basePoint(basePoint) + , m_penPos(0, 0) + , m_paraStartPos(0, 0) + , m_rfont(rfont) + , m_chars(str.UChars()) + , m_length(str.Length()) + , m_currentStartChar(0) + , m_currentSpanLength(0) + , m_committedLength(0) + , m_emitOffset(0) + , m_haveCommitted(false) + , m_maxSpanWidth(spanWidth) + { + } + + TextPlacer::~TextPlacer() + { + } + + bool TextPlacer::PlaceGlyph(GlyphPlacementCharacteristics &outCharacteristics) + { + for (;;) + { + if (!m_haveCommitted) + { + if (m_currentStartChar == m_length) + return false; + + size_t lastWhitespace = m_currentStartChar; + bool shouldSkipSpaces = false; + + size_t committedLength = 0; + + size_t i = m_currentStartChar; + + // Find a span to print + int32_t spanWidth = 0; + for (;;) + { + if (i == m_length) + { + committedLength = i - m_currentStartChar; + break; + } + + uint8_t character = m_chars[i]; + + if (character <= ' ') + { + committedLength = i - m_currentStartChar + 1; + if (character == '\r') + break; + + const PortabilityLayer::RenderedGlyphMetrics *metrics = nullptr; + const void *glyphData = nullptr; + if (m_rfont->GetGlyph(m_chars[i], metrics, glyphData)) + spanWidth += metrics->m_advanceX; + + i++; + } + else + { + const PortabilityLayer::RenderedGlyphMetrics *metrics = nullptr; + const void *glyphData = nullptr; + if (!m_rfont->GetGlyph(m_chars[i], metrics, glyphData)) + { + i++; + continue; + } + + spanWidth += metrics->m_advanceX; + + if (m_maxSpanWidth >= 0 && spanWidth > m_maxSpanWidth) + { + if (committedLength == 0) + { + // Word didn't fit + committedLength = i - m_currentStartChar; + if (committedLength == 0) + committedLength = 1; // Nothing fit, consume one char + } + + break; + } + + i++; + } + } + + m_haveCommitted = true; + m_committedLength = committedLength; + m_emitOffset = 0; + } + + assert(m_emitOffset < m_committedLength); + + const size_t charIndex = m_currentStartChar + m_emitOffset; + const uint8_t character = m_chars[charIndex]; + + outCharacteristics.m_glyphStartPos = m_penPos + m_basePoint; + outCharacteristics.m_glyphEndPos = outCharacteristics.m_glyphStartPos; + outCharacteristics.m_isParaStart = (m_emitOffset == 0); + outCharacteristics.m_isParaEnd = (m_emitOffset == m_committedLength - 1); + outCharacteristics.m_characterIndex = m_currentStartChar + m_emitOffset; + outCharacteristics.m_character = character; + + const PortabilityLayer::RenderedGlyphMetrics *metrics; + const void *data; + if (!m_rfont->GetGlyph(character, metrics, data)) + { + outCharacteristics.m_haveGlyph = false; + outCharacteristics.m_glyphMetrics = nullptr; + outCharacteristics.m_glyphData = nullptr; + } + else + { + m_penPos.m_x += metrics->m_advanceX; + + outCharacteristics.m_haveGlyph = true; + outCharacteristics.m_glyphMetrics = metrics; + outCharacteristics.m_glyphData = data; + outCharacteristics.m_glyphEndPos.m_x += metrics->m_advanceX; + } + + m_emitOffset++; + + if (m_emitOffset == m_committedLength) + { + m_currentStartChar += m_committedLength; + m_haveCommitted = false; + + m_paraStartPos.m_y += m_rfont->GetMetrics().m_linegap; + m_penPos = m_paraStartPos; + } + + return true; + } + } +} diff --git a/PortabilityLayer/TextPlacer.h b/PortabilityLayer/TextPlacer.h new file mode 100644 index 0000000..93c8b99 --- /dev/null +++ b/PortabilityLayer/TextPlacer.h @@ -0,0 +1,49 @@ +#pragma once + +#include "Vec2i.h" + +class PLPasStr; + +namespace PortabilityLayer +{ + class RenderedFont; + struct RenderedGlyphMetrics; + + struct GlyphPlacementCharacteristics + { + bool m_haveGlyph; + bool m_isParaStart; // Character is the first character in the paragraph + bool m_isParaEnd; // Character is the last character in the paragraph + const RenderedGlyphMetrics *m_glyphMetrics; // Glyph metrics + const void *m_glyphData; // Glyph data + Vec2i m_glyphStartPos; // Glyph start position + Vec2i m_glyphEndPos; // Glyph end position + unsigned int m_character; // Character code + size_t m_characterIndex; // Index in the input string + }; + + class TextPlacer + { + public: + TextPlacer(const Vec2i &basePoint, int32_t spanWidth, PortabilityLayer::RenderedFont *rfont, const PLPasStr &str); + ~TextPlacer(); + + bool PlaceGlyph(GlyphPlacementCharacteristics &outCharacteristics); + + private: + Vec2i m_basePoint; + Vec2i m_penPos; + Vec2i m_paraStartPos; + PortabilityLayer::RenderedFont *m_rfont; + const uint8_t *m_chars; + size_t m_length; + + size_t m_currentStartChar; + size_t m_currentSpanLength; + size_t m_committedLength; + size_t m_emitOffset; + bool m_haveCommitted; + + int32_t m_maxSpanWidth; + }; +}