From 9cb60768fed118339b6cebfbaf3ca2c1461eb774 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 1 May 2023 14:42:51 +0200 Subject: [PATCH] Codechange: split implementations of ParagraphLayouterFactory into their own file --- src/CMakeLists.txt | 8 + src/gfx_layout.cpp | 551 +----------------------------------- src/gfx_layout_fallback.cpp | 376 ++++++++++++++++++++++++ src/gfx_layout_fallback.h | 30 ++ src/gfx_layout_icu.cpp | 181 ++++++++++++ src/gfx_layout_icu.h | 30 ++ 6 files changed, 628 insertions(+), 548 deletions(-) create mode 100644 src/gfx_layout_fallback.cpp create mode 100644 src/gfx_layout_fallback.h create mode 100644 src/gfx_layout_icu.cpp create mode 100644 src/gfx_layout_icu.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6bd62e4224..ce078a02af 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,12 @@ add_files( CONDITION SSE_FOUND ) +add_files( + gfx_layout_icu.cpp + gfx_layout_icu.h + CONDITION ICU_lx_FOUND +) + add_files( aircraft.h aircraft_cmd.cpp @@ -175,6 +181,8 @@ add_files( gfx_func.h gfx_layout.cpp gfx_layout.h + gfx_layout_fallback.cpp + gfx_layout_fallback.h gfx_type.h gfxinit.cpp gfxinit.h diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp index 1a163330d2..ab40b55737 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -16,8 +16,10 @@ #include "table/control_codes.h" +#include "gfx_layout_fallback.h" + #ifdef WITH_ICU_LX -#include +#include "gfx_layout_icu.h" #endif /* WITH_ICU_LX */ #ifdef WITH_UNISCRIBE @@ -49,553 +51,6 @@ Font::Font(FontSize size, TextColour colour) : assert(size < FS_END); } -#ifdef WITH_ICU_LX -/* Implementation details of LEFontInstance */ - -le_int32 Font::getUnitsPerEM() const -{ - return this->fc->GetUnitsPerEM(); -} - -le_int32 Font::getAscent() const -{ - return this->fc->GetAscender(); -} - -le_int32 Font::getDescent() const -{ - return -this->fc->GetDescender(); -} - -le_int32 Font::getLeading() const -{ - return this->fc->GetHeight(); -} - -float Font::getXPixelsPerEm() const -{ - return (float)this->fc->GetHeight(); -} - -float Font::getYPixelsPerEm() const -{ - return (float)this->fc->GetHeight(); -} - -float Font::getScaleFactorX() const -{ - return 1.0f; -} - -float Font::getScaleFactorY() const -{ - return 1.0f; -} - -const void *Font::getFontTable(LETag tableTag) const -{ - size_t length; - return this->getFontTable(tableTag, length); -} - -const void *Font::getFontTable(LETag tableTag, size_t &length) const -{ - return this->fc->GetFontTable(tableTag, length); -} - -LEGlyphID Font::mapCharToGlyph(LEUnicode32 ch) const -{ - if (IsTextDirectionChar(ch)) return 0; - return this->fc->MapCharToGlyph(ch); -} - -void Font::getGlyphAdvance(LEGlyphID glyph, LEPoint &advance) const -{ - advance.fX = glyph == 0xFFFF ? 0 : this->fc->GetGlyphWidth(glyph); - advance.fY = 0; -} - -le_bool Font::getGlyphPoint(LEGlyphID glyph, le_int32 pointNumber, LEPoint &point) const -{ - return false; -} - -/** - * Wrapper for doing layouts with ICU. - */ -class ICUParagraphLayout : public ParagraphLayouter { - icu::ParagraphLayout *p; ///< The actual ICU paragraph layout. -public: - /** Visual run contains data about the bit of text with the same font. */ - class ICUVisualRun : public ParagraphLayouter::VisualRun { - const icu::ParagraphLayout::VisualRun *vr; ///< The actual ICU vr. - - public: - ICUVisualRun(const icu::ParagraphLayout::VisualRun *vr) : vr(vr) { } - - const Font *GetFont() const override { return (const Font*)vr->getFont(); } - int GetGlyphCount() const override { return vr->getGlyphCount(); } - const GlyphID *GetGlyphs() const override { return vr->getGlyphs(); } - const float *GetPositions() const override { return vr->getPositions(); } - int GetLeading() const override { return vr->getLeading(); } - const int *GetGlyphToCharMap() const override { return vr->getGlyphToCharMap(); } - }; - - /** A single line worth of VisualRuns. */ - class ICULine : public std::vector, public ParagraphLayouter::Line { - icu::ParagraphLayout::Line *l; ///< The actual ICU line. - - public: - ICULine(icu::ParagraphLayout::Line *l) : l(l) - { - for (int i = 0; i < l->countRuns(); i++) { - this->emplace_back(l->getVisualRun(i)); - } - } - ~ICULine() override { delete l; } - - int GetLeading() const override { return l->getLeading(); } - int GetWidth() const override { return l->getWidth(); } - int CountRuns() const override { return l->countRuns(); } - const ParagraphLayouter::VisualRun &GetVisualRun(int run) const override { return this->at(run); } - - int GetInternalCharLength(WChar c) const override - { - /* ICU uses UTF-16 internally which means we need to account for surrogate pairs. */ - return Utf8CharLen(c) < 4 ? 1 : 2; - } - }; - - ICUParagraphLayout(icu::ParagraphLayout *p) : p(p) { } - ~ICUParagraphLayout() override { delete p; } - void Reflow() override { p->reflow(); } - - std::unique_ptr NextLine(int max_width) override - { - icu::ParagraphLayout::Line *l = p->nextLine(max_width); - return std::unique_ptr(l == nullptr ? nullptr : new ICULine(l)); - } -}; - -/** - * Helper class to construct a new #ICUParagraphLayout. - */ -class ICUParagraphLayoutFactory { -public: - /** Helper for GetLayouter, to get the right type. */ - typedef UChar CharType; - /** Helper for GetLayouter, to get whether the layouter supports RTL. */ - static const bool SUPPORTS_RTL = true; - - static ParagraphLayouter *GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping) - { - int32 length = buff_end - buff; - - if (length == 0) { - /* ICU's ParagraphLayout cannot handle empty strings, so fake one. */ - buff[0] = ' '; - length = 1; - fontMapping.back().first++; - } - - /* Fill ICU's FontRuns with the right data. */ - icu::FontRuns runs(fontMapping.size()); - for (auto &pair : fontMapping) { - runs.add(pair.second, pair.first); - } - - LEErrorCode status = LE_NO_ERROR; - /* ParagraphLayout does not copy "buff", so it must stay valid. - * "runs" is copied according to the ICU source, but the documentation does not specify anything, so this might break somewhen. */ - icu::ParagraphLayout *p = new icu::ParagraphLayout(buff, length, &runs, nullptr, nullptr, nullptr, _current_text_dir == TD_RTL ? 1 : 0, false, status); - if (status != LE_NO_ERROR) { - delete p; - return nullptr; - } - - return new ICUParagraphLayout(p); - } - - static size_t AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c) - { - /* Transform from UTF-32 to internal ICU format of UTF-16. */ - int32 length = 0; - UErrorCode err = U_ZERO_ERROR; - u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err); - return length; - } -}; -#endif /* WITH_ICU_LX */ - -/*** Paragraph layout ***/ -/** - * Class handling the splitting of a paragraph of text into lines and - * visual runs. - * - * One constructs this class with the text that needs to be split into - * lines. Then nextLine is called with the maximum width until nullptr is - * returned. Each nextLine call creates VisualRuns which contain the - * length of text that are to be drawn with the same font. In other - * words, the result of this class is a list of sub strings with their - * font. The sub strings are then already fully laid out, and only - * need actual drawing. - * - * The positions in a visual run are sequential pairs of X,Y of the - * begin of each of the glyphs plus an extra pair to mark the end. - * - * @note This variant does not handle left-to-right properly. This - * is supported in the one ParagraphLayout coming from ICU. - */ -class FallbackParagraphLayout : public ParagraphLayouter { -public: - /** Visual run contains data about the bit of text with the same font. */ - class FallbackVisualRun : public ParagraphLayouter::VisualRun { - Font *font; ///< The font used to layout these. - GlyphID *glyphs; ///< The glyphs we're drawing. - float *positions; ///< The positions of the glyphs. - int *glyph_to_char; ///< The char index of the glyphs. - int glyph_count; ///< The number of glyphs. - - public: - FallbackVisualRun(Font *font, const WChar *chars, int glyph_count, int x); - FallbackVisualRun(FallbackVisualRun &&other) noexcept; - ~FallbackVisualRun() override; - const Font *GetFont() const override; - int GetGlyphCount() const override; - const GlyphID *GetGlyphs() const override; - const float *GetPositions() const override; - int GetLeading() const override; - const int *GetGlyphToCharMap() const override; - }; - - /** A single line worth of VisualRuns. */ - class FallbackLine : public std::vector, public ParagraphLayouter::Line { - public: - int GetLeading() const override; - int GetWidth() const override; - int CountRuns() const override; - const ParagraphLayouter::VisualRun &GetVisualRun(int run) const override; - - int GetInternalCharLength(WChar c) const override { return 1; } - }; - - const WChar *buffer_begin; ///< Begin of the buffer. - const WChar *buffer; ///< The current location in the buffer. - FontMap &runs; ///< The fonts we have to use for this paragraph. - - FallbackParagraphLayout(WChar *buffer, int length, FontMap &runs); - void Reflow() override; - std::unique_ptr NextLine(int max_width) override; -}; - -/** - * Helper class to construct a new #FallbackParagraphLayout. - */ -class FallbackParagraphLayoutFactory { -public: - /** Helper for GetLayouter, to get the right type. */ - typedef WChar CharType; - /** Helper for GetLayouter, to get whether the layouter supports RTL. */ - static const bool SUPPORTS_RTL = false; - - /** - * Get the actual ParagraphLayout for the given buffer. - * @param buff The begin of the buffer. - * @param buff_end The location after the last element in the buffer. - * @param fontMapping THe mapping of the fonts. - * @return The ParagraphLayout instance. - */ - static ParagraphLayouter *GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping) - { - return new FallbackParagraphLayout(buff, buff_end - buff, fontMapping); - } - - /** - * Append a wide character to the internal buffer. - * @param buff The buffer to append to. - * @param buffer_last The end of the buffer. - * @param c The character to add. - * @return The number of buffer spaces that were used. - */ - static size_t AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c) - { - *buff = c; - return 1; - } -}; - -/** - * Create the visual run. - * @param font The font to use for this run. - * @param chars The characters to use for this run. - * @param char_count The number of characters in this run. - * @param x The initial x position for this run. - */ -FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(Font *font, const WChar *chars, int char_count, int x) : - font(font), glyph_count(char_count) -{ - const bool isbuiltin = font->fc->IsBuiltInFont(); - - this->glyphs = MallocT(this->glyph_count); - this->glyph_to_char = MallocT(this->glyph_count); - - /* Positions contains the location of the begin of each of the glyphs, and the end of the last one. */ - this->positions = MallocT(this->glyph_count * 2 + 2); - this->positions[0] = x; - - for (int i = 0; i < this->glyph_count; i++) { - this->glyphs[i] = font->fc->MapCharToGlyph(chars[i]); - if (isbuiltin) { - this->positions[2 * i + 1] = font->fc->GetAscender(); // Apply sprite font's ascender. - } else if (chars[i] >= SCC_SPRITE_START && chars[i] <= SCC_SPRITE_END) { - this->positions[2 * i + 1] = (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2; // Align sprite font to centre - } else { - this->positions[2 * i + 1] = 0; // No ascender adjustment. - } - this->positions[2 * i + 2] = this->positions[2 * i] + font->fc->GetGlyphWidth(this->glyphs[i]); - this->glyph_to_char[i] = i; - } -} - -/** Move constructor for visual runs.*/ -FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(FallbackVisualRun &&other) noexcept : font(other.font), glyph_count(other.glyph_count) -{ - this->positions = other.positions; - this->glyph_to_char = other.glyph_to_char; - this->glyphs = other.glyphs; - - other.positions = nullptr; - other.glyph_to_char = nullptr; - other.glyphs = nullptr; -} - -/** Free all data. */ -FallbackParagraphLayout::FallbackVisualRun::~FallbackVisualRun() -{ - free(this->positions); - free(this->glyph_to_char); - free(this->glyphs); -} - -/** - * Get the font associated with this run. - * @return The font. - */ -const Font *FallbackParagraphLayout::FallbackVisualRun::GetFont() const -{ - return this->font; -} - -/** - * Get the number of glyphs in this run. - * @return The number of glyphs. - */ -int FallbackParagraphLayout::FallbackVisualRun::GetGlyphCount() const -{ - return this->glyph_count; -} - -/** - * Get the glyphs of this run. - * @return The glyphs. - */ -const GlyphID *FallbackParagraphLayout::FallbackVisualRun::GetGlyphs() const -{ - return this->glyphs; -} - -/** - * Get the positions of this run. - * @return The positions. - */ -const float *FallbackParagraphLayout::FallbackVisualRun::GetPositions() const -{ - return this->positions; -} - -/** - * Get the glyph-to-character map for this visual run. - * @return The glyph-to-character map. - */ -const int *FallbackParagraphLayout::FallbackVisualRun::GetGlyphToCharMap() const -{ - return this->glyph_to_char; -} - -/** - * Get the height of this font. - * @return The height of the font. - */ -int FallbackParagraphLayout::FallbackVisualRun::GetLeading() const -{ - return this->GetFont()->fc->GetHeight(); -} - -/** - * Get the height of the line. - * @return The maximum height of the line. - */ -int FallbackParagraphLayout::FallbackLine::GetLeading() const -{ - int leading = 0; - for (const auto &run : *this) { - leading = std::max(leading, run.GetLeading()); - } - - return leading; -} - -/** - * Get the width of this line. - * @return The width of the line. - */ -int FallbackParagraphLayout::FallbackLine::GetWidth() const -{ - if (this->size() == 0) return 0; - - /* - * The last X position of a run contains is the end of that run. - * Since there is no left-to-right support, taking this value of - * the last run gives us the end of the line and thus the width. - */ - const auto &run = this->GetVisualRun(this->CountRuns() - 1); - return (int)run.GetPositions()[run.GetGlyphCount() * 2]; -} - -/** - * Get the number of runs in this line. - * @return The number of runs. - */ -int FallbackParagraphLayout::FallbackLine::CountRuns() const -{ - return (uint)this->size(); -} - -/** - * Get a specific visual run. - * @return The visual run. - */ -const ParagraphLayouter::VisualRun &FallbackParagraphLayout::FallbackLine::GetVisualRun(int run) const -{ - return this->at(run); -} - -/** - * Create a new paragraph layouter. - * @param buffer The characters of the paragraph. - * @param length The length of the paragraph. - * @param runs The font mapping of this paragraph. - */ -FallbackParagraphLayout::FallbackParagraphLayout(WChar *buffer, int length, FontMap &runs) : buffer_begin(buffer), buffer(buffer), runs(runs) -{ - assert(runs.End()[-1].first == length); -} - -/** - * Reset the position to the start of the paragraph. - */ -void FallbackParagraphLayout::Reflow() -{ - this->buffer = this->buffer_begin; -} - -/** - * Construct a new line with a maximum width. - * @param max_width The maximum width of the string. - * @return A Line, or nullptr when at the end of the paragraph. - */ -std::unique_ptr FallbackParagraphLayout::NextLine(int max_width) -{ - /* Simple idea: - * - split a line at a newline character, or at a space where we can break a line. - * - split for a visual run whenever a new line happens, or the font changes. - */ - if (this->buffer == nullptr) return nullptr; - - std::unique_ptr l(new FallbackLine()); - - if (*this->buffer == '\0') { - /* Only a newline. */ - this->buffer = nullptr; - l->emplace_back(this->runs.front().second, this->buffer, 0, 0); - return l; - } - - int offset = this->buffer - this->buffer_begin; - FontMap::iterator iter = this->runs.data(); - while (iter->first <= offset) { - iter++; - assert(iter != this->runs.End()); - } - - const FontCache *fc = iter->second->fc; - const WChar *next_run = this->buffer_begin + iter->first; - - const WChar *begin = this->buffer; - const WChar *last_space = nullptr; - const WChar *last_char; - int width = 0; - for (;;) { - WChar c = *this->buffer; - last_char = this->buffer; - - if (c == '\0') { - this->buffer = nullptr; - break; - } - - if (this->buffer == next_run) { - int w = l->GetWidth(); - l->emplace_back(iter->second, begin, this->buffer - begin, w); - iter++; - assert(iter != this->runs.End()); - - next_run = this->buffer_begin + iter->first; - begin = this->buffer; - } - - if (IsWhitespace(c)) last_space = this->buffer; - - if (IsPrintable(c) && !IsTextDirectionChar(c)) { - int char_width = GetCharacterWidth(fc->GetSize(), c); - width += char_width; - if (width > max_width) { - /* The string is longer than maximum width so we need to decide - * what to do with it. */ - if (width == char_width) { - /* The character is wider than allowed width; don't know - * what to do with this case... bail out! */ - this->buffer = nullptr; - return l; - } - - if (last_space == nullptr) { - /* No space has been found. Just terminate at our current - * location. This usually happens for languages that do not - * require spaces in strings, like Chinese, Japanese and - * Korean. For other languages terminating mid-word might - * not be the best, but terminating the whole string instead - * of continuing the word at the next line is worse. */ - last_char = this->buffer; - } else { - /* A space is found; perfect place to terminate */ - this->buffer = last_space + 1; - last_char = last_space; - } - break; - } - } - - this->buffer++; - } - - if (l->size() == 0 || last_char - begin > 0) { - int w = l->GetWidth(); - l->emplace_back(iter->second, begin, last_char - begin, w); - } - return l; -} - /** * Helper for getting a ParagraphLayouter of the given type. * diff --git a/src/gfx_layout_fallback.cpp b/src/gfx_layout_fallback.cpp new file mode 100644 index 0000000000..2274ad1637 --- /dev/null +++ b/src/gfx_layout_fallback.cpp @@ -0,0 +1,376 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file gfx_layout_fallback.cpp Handling of laying out text as fallback. */ + +#include "stdafx.h" + +#include "gfx_layout_fallback.h" +#include "string_func.h" +#include "zoom_func.h" + +#include "table/control_codes.h" + +#include "safeguards.h" + +/*** Paragraph layout ***/ +/** + * Class handling the splitting of a paragraph of text into lines and + * visual runs. + * + * One constructs this class with the text that needs to be split into + * lines. Then nextLine is called with the maximum width until nullptr is + * returned. Each nextLine call creates VisualRuns which contain the + * length of text that are to be drawn with the same font. In other + * words, the result of this class is a list of sub strings with their + * font. The sub strings are then already fully laid out, and only + * need actual drawing. + * + * The positions in a visual run are sequential pairs of X,Y of the + * begin of each of the glyphs plus an extra pair to mark the end. + * + * @note This variant does not handle left-to-right properly. This + * is supported in the one ParagraphLayout coming from ICU. + */ +class FallbackParagraphLayout : public ParagraphLayouter { +public: + /** Visual run contains data about the bit of text with the same font. */ + class FallbackVisualRun : public ParagraphLayouter::VisualRun { + Font *font; ///< The font used to layout these. + GlyphID *glyphs; ///< The glyphs we're drawing. + float *positions; ///< The positions of the glyphs. + int *glyph_to_char; ///< The char index of the glyphs. + int glyph_count; ///< The number of glyphs. + + public: + FallbackVisualRun(Font *font, const WChar *chars, int glyph_count, int x); + FallbackVisualRun(FallbackVisualRun &&other) noexcept; + ~FallbackVisualRun() override; + const Font *GetFont() const override; + int GetGlyphCount() const override; + const GlyphID *GetGlyphs() const override; + const float *GetPositions() const override; + int GetLeading() const override; + const int *GetGlyphToCharMap() const override; + }; + + /** A single line worth of VisualRuns. */ + class FallbackLine : public std::vector, public ParagraphLayouter::Line { + public: + int GetLeading() const override; + int GetWidth() const override; + int CountRuns() const override; + const ParagraphLayouter::VisualRun &GetVisualRun(int run) const override; + + int GetInternalCharLength(WChar c) const override { return 1; } + }; + + const WChar *buffer_begin; ///< Begin of the buffer. + const WChar *buffer; ///< The current location in the buffer. + FontMap &runs; ///< The fonts we have to use for this paragraph. + + FallbackParagraphLayout(WChar *buffer, int length, FontMap &runs); + void Reflow() override; + std::unique_ptr NextLine(int max_width) override; +}; + +/** + * Get the actual ParagraphLayout for the given buffer. + * @param buff The begin of the buffer. + * @param buff_end The location after the last element in the buffer. + * @param fontMapping THe mapping of the fonts. + * @return The ParagraphLayout instance. + */ +/* static */ ParagraphLayouter *FallbackParagraphLayoutFactory::GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping) +{ + return new FallbackParagraphLayout(buff, buff_end - buff, fontMapping); +} + +/** + * Append a wide character to the internal buffer. + * @param buff The buffer to append to. + * @param buffer_last The end of the buffer. + * @param c The character to add. + * @return The number of buffer spaces that were used. + */ +/* static */ size_t FallbackParagraphLayoutFactory::AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c) +{ + *buff = c; + return 1; +} + +/** + * Create the visual run. + * @param font The font to use for this run. + * @param chars The characters to use for this run. + * @param char_count The number of characters in this run. + * @param x The initial x position for this run. + */ +FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(Font *font, const WChar *chars, int char_count, int x) : + font(font), glyph_count(char_count) +{ + const bool isbuiltin = font->fc->IsBuiltInFont(); + + this->glyphs = MallocT(this->glyph_count); + this->glyph_to_char = MallocT(this->glyph_count); + + /* Positions contains the location of the begin of each of the glyphs, and the end of the last one. */ + this->positions = MallocT(this->glyph_count * 2 + 2); + this->positions[0] = x; + + for (int i = 0; i < this->glyph_count; i++) { + this->glyphs[i] = font->fc->MapCharToGlyph(chars[i]); + if (isbuiltin) { + this->positions[2 * i + 1] = font->fc->GetAscender(); // Apply sprite font's ascender. + } else if (chars[i] >= SCC_SPRITE_START && chars[i] <= SCC_SPRITE_END) { + this->positions[2 * i + 1] = (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2; // Align sprite font to centre + } else { + this->positions[2 * i + 1] = 0; // No ascender adjustment. + } + this->positions[2 * i + 2] = this->positions[2 * i] + font->fc->GetGlyphWidth(this->glyphs[i]); + this->glyph_to_char[i] = i; + } +} + +/** Move constructor for visual runs.*/ +FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(FallbackVisualRun &&other) noexcept : font(other.font), glyph_count(other.glyph_count) +{ + this->positions = other.positions; + this->glyph_to_char = other.glyph_to_char; + this->glyphs = other.glyphs; + + other.positions = nullptr; + other.glyph_to_char = nullptr; + other.glyphs = nullptr; +} + +/** Free all data. */ +FallbackParagraphLayout::FallbackVisualRun::~FallbackVisualRun() +{ + free(this->positions); + free(this->glyph_to_char); + free(this->glyphs); +} + +/** + * Get the font associated with this run. + * @return The font. + */ +const Font *FallbackParagraphLayout::FallbackVisualRun::GetFont() const +{ + return this->font; +} + +/** + * Get the number of glyphs in this run. + * @return The number of glyphs. + */ +int FallbackParagraphLayout::FallbackVisualRun::GetGlyphCount() const +{ + return this->glyph_count; +} + +/** + * Get the glyphs of this run. + * @return The glyphs. + */ +const GlyphID *FallbackParagraphLayout::FallbackVisualRun::GetGlyphs() const +{ + return this->glyphs; +} + +/** + * Get the positions of this run. + * @return The positions. + */ +const float *FallbackParagraphLayout::FallbackVisualRun::GetPositions() const +{ + return this->positions; +} + +/** + * Get the glyph-to-character map for this visual run. + * @return The glyph-to-character map. + */ +const int *FallbackParagraphLayout::FallbackVisualRun::GetGlyphToCharMap() const +{ + return this->glyph_to_char; +} + +/** + * Get the height of this font. + * @return The height of the font. + */ +int FallbackParagraphLayout::FallbackVisualRun::GetLeading() const +{ + return this->GetFont()->fc->GetHeight(); +} + +/** + * Get the height of the line. + * @return The maximum height of the line. + */ +int FallbackParagraphLayout::FallbackLine::GetLeading() const +{ + int leading = 0; + for (const auto &run : *this) { + leading = std::max(leading, run.GetLeading()); + } + + return leading; +} + +/** + * Get the width of this line. + * @return The width of the line. + */ +int FallbackParagraphLayout::FallbackLine::GetWidth() const +{ + if (this->size() == 0) return 0; + + /* + * The last X position of a run contains is the end of that run. + * Since there is no left-to-right support, taking this value of + * the last run gives us the end of the line and thus the width. + */ + const auto &run = this->GetVisualRun(this->CountRuns() - 1); + return (int)run.GetPositions()[run.GetGlyphCount() * 2]; +} + +/** + * Get the number of runs in this line. + * @return The number of runs. + */ +int FallbackParagraphLayout::FallbackLine::CountRuns() const +{ + return (uint)this->size(); +} + +/** + * Get a specific visual run. + * @return The visual run. + */ +const ParagraphLayouter::VisualRun &FallbackParagraphLayout::FallbackLine::GetVisualRun(int run) const +{ + return this->at(run); +} + +/** + * Create a new paragraph layouter. + * @param buffer The characters of the paragraph. + * @param length The length of the paragraph. + * @param runs The font mapping of this paragraph. + */ +FallbackParagraphLayout::FallbackParagraphLayout(WChar *buffer, int length, FontMap &runs) : buffer_begin(buffer), buffer(buffer), runs(runs) +{ + assert(runs.End()[-1].first == length); +} + +/** + * Reset the position to the start of the paragraph. + */ +void FallbackParagraphLayout::Reflow() +{ + this->buffer = this->buffer_begin; +} + +/** + * Construct a new line with a maximum width. + * @param max_width The maximum width of the string. + * @return A Line, or nullptr when at the end of the paragraph. + */ +std::unique_ptr FallbackParagraphLayout::NextLine(int max_width) +{ + /* Simple idea: + * - split a line at a newline character, or at a space where we can break a line. + * - split for a visual run whenever a new line happens, or the font changes. + */ + if (this->buffer == nullptr) return nullptr; + + std::unique_ptr l(new FallbackLine()); + + if (*this->buffer == '\0') { + /* Only a newline. */ + this->buffer = nullptr; + l->emplace_back(this->runs.front().second, this->buffer, 0, 0); + return l; + } + + int offset = this->buffer - this->buffer_begin; + FontMap::iterator iter = this->runs.data(); + while (iter->first <= offset) { + iter++; + assert(iter != this->runs.End()); + } + + const FontCache *fc = iter->second->fc; + const WChar *next_run = this->buffer_begin + iter->first; + + const WChar *begin = this->buffer; + const WChar *last_space = nullptr; + const WChar *last_char; + int width = 0; + for (;;) { + WChar c = *this->buffer; + last_char = this->buffer; + + if (c == '\0') { + this->buffer = nullptr; + break; + } + + if (this->buffer == next_run) { + int w = l->GetWidth(); + l->emplace_back(iter->second, begin, this->buffer - begin, w); + iter++; + assert(iter != this->runs.End()); + + next_run = this->buffer_begin + iter->first; + begin = this->buffer; + } + + if (IsWhitespace(c)) last_space = this->buffer; + + if (IsPrintable(c) && !IsTextDirectionChar(c)) { + int char_width = GetCharacterWidth(fc->GetSize(), c); + width += char_width; + if (width > max_width) { + /* The string is longer than maximum width so we need to decide + * what to do with it. */ + if (width == char_width) { + /* The character is wider than allowed width; don't know + * what to do with this case... bail out! */ + this->buffer = nullptr; + return l; + } + + if (last_space == nullptr) { + /* No space has been found. Just terminate at our current + * location. This usually happens for languages that do not + * require spaces in strings, like Chinese, Japanese and + * Korean. For other languages terminating mid-word might + * not be the best, but terminating the whole string instead + * of continuing the word at the next line is worse. */ + last_char = this->buffer; + } else { + /* A space is found; perfect place to terminate */ + this->buffer = last_space + 1; + last_char = last_space; + } + break; + } + } + + this->buffer++; + } + + if (l->size() == 0 || last_char - begin > 0) { + int w = l->GetWidth(); + l->emplace_back(iter->second, begin, last_char - begin, w); + } + return l; +} diff --git a/src/gfx_layout_fallback.h b/src/gfx_layout_fallback.h new file mode 100644 index 0000000000..15927ac001 --- /dev/null +++ b/src/gfx_layout_fallback.h @@ -0,0 +1,30 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file gfx_layout_fallback.h Functions related to laying out the texts as fallback. */ + +#ifndef GFX_LAYOUT_FALLBACK_H +#define GFX_LAYOUT_FALLBACK_H + +#include "gfx_layout.h" + +/** + * Helper class to construct a new #FallbackParagraphLayout. + */ +class FallbackParagraphLayoutFactory { +public: + /** Helper for GetLayouter, to get the right type. */ + typedef WChar CharType; + /** Helper for GetLayouter, to get whether the layouter supports RTL. */ + static const bool SUPPORTS_RTL = false; + + static ParagraphLayouter *GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping); + static size_t AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c); +}; + + +#endif /* GFX_LAYOUT_FALLBACK_H */ diff --git a/src/gfx_layout_icu.cpp b/src/gfx_layout_icu.cpp new file mode 100644 index 0000000000..f88ab51330 --- /dev/null +++ b/src/gfx_layout_icu.cpp @@ -0,0 +1,181 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file gfx_layout_icu.cpp Handling of laying out text with ICU. */ + +#include "stdafx.h" + +#include "gfx_layout_icu.h" + +#include + +#include "safeguards.h" + +/* Implementation details of LEFontInstance */ + +le_int32 Font::getUnitsPerEM() const +{ + return this->fc->GetUnitsPerEM(); +} + +le_int32 Font::getAscent() const +{ + return this->fc->GetAscender(); +} + +le_int32 Font::getDescent() const +{ + return -this->fc->GetDescender(); +} + +le_int32 Font::getLeading() const +{ + return this->fc->GetHeight(); +} + +float Font::getXPixelsPerEm() const +{ + return (float)this->fc->GetHeight(); +} + +float Font::getYPixelsPerEm() const +{ + return (float)this->fc->GetHeight(); +} + +float Font::getScaleFactorX() const +{ + return 1.0f; +} + +float Font::getScaleFactorY() const +{ + return 1.0f; +} + +const void *Font::getFontTable(LETag tableTag) const +{ + size_t length; + return this->getFontTable(tableTag, length); +} + +const void *Font::getFontTable(LETag tableTag, size_t &length) const +{ + return this->fc->GetFontTable(tableTag, length); +} + +LEGlyphID Font::mapCharToGlyph(LEUnicode32 ch) const +{ + if (IsTextDirectionChar(ch)) return 0; + return this->fc->MapCharToGlyph(ch); +} + +void Font::getGlyphAdvance(LEGlyphID glyph, LEPoint &advance) const +{ + advance.fX = glyph == 0xFFFF ? 0 : this->fc->GetGlyphWidth(glyph); + advance.fY = 0; +} + +le_bool Font::getGlyphPoint(LEGlyphID glyph, le_int32 pointNumber, LEPoint &point) const +{ + return false; +} + +/** + * Wrapper for doing layouts with ICU. + */ +class ICUParagraphLayout : public ParagraphLayouter { + icu::ParagraphLayout *p; ///< The actual ICU paragraph layout. +public: + /** Visual run contains data about the bit of text with the same font. */ + class ICUVisualRun : public ParagraphLayouter::VisualRun { + const icu::ParagraphLayout::VisualRun *vr; ///< The actual ICU vr. + + public: + ICUVisualRun(const icu::ParagraphLayout::VisualRun *vr) : vr(vr) { } + + const Font *GetFont() const override { return (const Font*)vr->getFont(); } + int GetGlyphCount() const override { return vr->getGlyphCount(); } + const GlyphID *GetGlyphs() const override { return vr->getGlyphs(); } + const float *GetPositions() const override { return vr->getPositions(); } + int GetLeading() const override { return vr->getLeading(); } + const int *GetGlyphToCharMap() const override { return vr->getGlyphToCharMap(); } + }; + + /** A single line worth of VisualRuns. */ + class ICULine : public std::vector, public ParagraphLayouter::Line { + icu::ParagraphLayout::Line *l; ///< The actual ICU line. + + public: + ICULine(icu::ParagraphLayout::Line *l) : l(l) + { + for (int i = 0; i < l->countRuns(); i++) { + this->emplace_back(l->getVisualRun(i)); + } + } + ~ICULine() override { delete l; } + + int GetLeading() const override { return l->getLeading(); } + int GetWidth() const override { return l->getWidth(); } + int CountRuns() const override { return l->countRuns(); } + const ParagraphLayouter::VisualRun &GetVisualRun(int run) const override { return this->at(run); } + + int GetInternalCharLength(WChar c) const override + { + /* ICU uses UTF-16 internally which means we need to account for surrogate pairs. */ + return Utf8CharLen(c) < 4 ? 1 : 2; + } + }; + + ICUParagraphLayout(icu::ParagraphLayout *p) : p(p) { } + ~ICUParagraphLayout() override { delete p; } + void Reflow() override { p->reflow(); } + + std::unique_ptr NextLine(int max_width) override + { + icu::ParagraphLayout::Line *l = p->nextLine(max_width); + return std::unique_ptr(l == nullptr ? nullptr : new ICULine(l)); + } +}; + +/* static */ ParagraphLayouter *ICUParagraphLayoutFactory::GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping) +{ + int32 length = buff_end - buff; + + if (length == 0) { + /* ICU's ParagraphLayout cannot handle empty strings, so fake one. */ + buff[0] = ' '; + length = 1; + fontMapping.back().first++; + } + + /* Fill ICU's FontRuns with the right data. */ + icu::FontRuns runs(fontMapping.size()); + for (auto &pair : fontMapping) { + runs.add(pair.second, pair.first); + } + + LEErrorCode status = LE_NO_ERROR; + /* ParagraphLayout does not copy "buff", so it must stay valid. + * "runs" is copied according to the ICU source, but the documentation does not specify anything, so this might break somewhen. */ + icu::ParagraphLayout *p = new icu::ParagraphLayout(buff, length, &runs, nullptr, nullptr, nullptr, _current_text_dir == TD_RTL ? 1 : 0, false, status); + if (status != LE_NO_ERROR) { + delete p; + return nullptr; + } + + return new ICUParagraphLayout(p); +} + +/* static */ size_t ICUParagraphLayoutFactory::AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c) +{ + /* Transform from UTF-32 to internal ICU format of UTF-16. */ + int32 length = 0; + UErrorCode err = U_ZERO_ERROR; + u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err); + return length; +} diff --git a/src/gfx_layout_icu.h b/src/gfx_layout_icu.h new file mode 100644 index 0000000000..cdebcbc0b4 --- /dev/null +++ b/src/gfx_layout_icu.h @@ -0,0 +1,30 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file gfx_layout_icu.h Functions related to laying out the texts with ICU. */ + +#ifndef GFX_LAYOUT_ICU_H +#define GFX_LAYOUT_ICU_H + +#include "gfx_layout.h" +#include + +/** + * Helper class to construct a new #ICUParagraphLayout. + */ +class ICUParagraphLayoutFactory { +public: + /** Helper for GetLayouter, to get the right type. */ + typedef UChar CharType; + /** Helper for GetLayouter, to get whether the layouter supports RTL. */ + static const bool SUPPORTS_RTL = true; + + static ParagraphLayouter *GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping); + static size_t AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c); +}; + +#endif /* GFX_LAYOUT_ICU_H */