Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
10 KB
Referenced Files
None
Subscribers
None
diff --git a/src/WordWrapper.cpp b/src/WordWrapper.cpp
new file mode 100644
index 0000000..3575f68
--- /dev/null
+++ b/src/WordWrapper.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2019 Me and My Shadow
+ *
+ * This file is part of Me and My Shadow.
+ *
+ * Me and My Shadow 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "WordWrapper.h"
+#include "HyphenationManager.h"
+#include "HyphenationRule.h"
+#include "UTF8Functions.h"
+
+#include <assert.h>
+
+#include <SDL_ttf_fontfallback.h>
+
+int WordWrapper::getTextWidth(const std::string& s) {
+ if (s.empty()) return 0;
+
+ int w = 0;
+
+ if (font) {
+ TTF_SizeUTF8(font, s.c_str(), &w, NULL);
+ } else {
+ const size_t m = s.size();
+
+ U8STRING_FOR_EACH_CHARACTER_DO_BEGIN(s, i, m, ch, REPLACEMENT_CHARACTER);
+ w++;
+ U8STRING_FOR_EACH_CHARACTER_DO_END();
+ }
+
+ return w;
+}
+
+int WordWrapper::getGlyphWidth(int ch) {
+ if (font) {
+ int w = 0;
+ TTF_GlyphMetrics(font, ch, NULL, NULL, NULL, NULL, &w);
+ return w;
+ } else {
+ return 1;
+ }
+}
+
+WordWrapper::WordWrapper()
+ : font(NULL)
+ , maxWidth(0)
+ , wordWrap(false)
+ , reserveHyperlinks(false)
+{
+}
+
+WordWrapper::~WordWrapper() {
+}
+
+bool WordWrapper::isReserved(const std::string& word) {
+ if (reserveHyperlinks) {
+ const char *s = word.c_str();
+ const size_t m = word.size();
+ for (size_t i = 0; i < m; i++) {
+ // we only support http or https
+ if ((s[i] == 'H' || s[i] == 'h')
+ && (s[i + 1] == 'T' || s[i + 1] == 't')
+ && (s[i + 2] == 'T' || s[i + 2] == 't')
+ && (s[i + 3] == 'P' || s[i + 3] == 'p'))
+ {
+ if (s[i + 4] == ':' && s[i + 5] == '/' && s[i + 6] == '/') {
+ // http
+ return true;
+ } else if ((s[i + 4] == 'S' || s[i + 4] == 's') && s[i + 5] == ':' && s[i + 6] == '/' && s[i + 7] == '/') {
+ // https
+ return true;
+ }
+ }
+ }
+ }
+
+ for (const std::string& s : reservedWords) {
+ if (word == s) return true;
+ }
+
+ return false;
+}
+
+void WordWrapper::addString(std::vector<std::string>& output, const std::string& input) {
+ std::string line;
+
+ for (char c : input) {
+ if (c == '\r') {
+ } else if (c == '\n') {
+ addLine(output, line);
+ line.clear();
+ } else {
+ line.push_back(c);
+ }
+ }
+
+ addLine(output, line);
+}
+
+// Add a word to line, output the line only if the line+newWord doesn't fit the width and in this case put the newWord to the line.
+void WordWrapper::addWord(std::vector<std::string>& output, std::string& line, int& lineWidth, const std::string& spaces, const std::string& nonSpaces) {
+ int w1 = getTextWidth(spaces);
+
+ {
+ int w2 = getTextWidth(nonSpaces);
+
+ //Check if it fits into current line.
+ if (lineWidth + w1 + w2 <= maxWidth) {
+ line += spaces + nonSpaces;
+ lineWidth += w1 + w2;
+ return;
+ }
+
+ //Now it doesn't fit into current line.
+
+ //Check if we should skip the hyphenation.
+ if (hyphen.empty() || isReserved(nonSpaces)) {
+ if (line.empty()) {
+ //A line consists of at least one word, so we append it forcefully.
+ line += spaces + nonSpaces;
+ lineWidth += w1 + w2;
+ } else {
+ //We output current line.
+ output.push_back(line);
+
+ //And add a new line consisting of new word (but we remove spaces in it).
+ line = nonSpaces;
+ lineWidth = w2;
+ }
+ return;
+ }
+ }
+
+ auto hm = getHyphenationManager();
+ auto hyphenator = hyphenatorLanguage.empty() ? hm->getHyphenator() : hm->getHyphenator(hyphenatorLanguage);
+ auto rules = hyphenator->applyHyphenationRules(nonSpaces);
+
+ const size_t m = nonSpaces.size();
+
+ std::string tmp, prev;
+ int skip = 0, prevSkip = 0, prevWidth = 0;
+ size_t prevIndex = 0;
+
+ for (size_t i = 0;; i++) {
+ const Hyphenate::HyphenationRule *rule = (i < m) ? (*rules)[i] : NULL;
+ if (rule || i == m) {
+ std::string tmp2 = tmp;
+ if (rule) rule->apply_first(tmp2, hyphen);
+
+ int newWidth = getTextWidth(tmp2);
+
+ //debug
+ printf("%-5d %s\n", newWidth, tmp2.c_str());
+
+ //Check if we should output current line directly.
+ if (lineWidth + w1 + newWidth > maxWidth && prev.empty() && !line.empty()) {
+ //We output current line.
+ output.push_back(line);
+
+ line.clear();
+ lineWidth = 0;
+ w1 = 0;
+ }
+
+ //Check if the line is still too long.
+ if (lineWidth + w1 + newWidth > maxWidth) {
+ //Check if we have previous available hyphenation
+ if (prev.empty()) {
+ //Line is empty, we have to append it forcefully.
+ assert(line.empty());
+
+ if (w1 > 0) line += spaces;
+ line += tmp2;
+ if (i < m) {
+ output.push_back(line);
+ line.clear();
+ lineWidth = 0;
+ w1 = 0;
+ } else {
+ lineWidth += w1 + newWidth;
+ }
+
+ //Update buffer
+ tmp.clear();
+ if (rule) skip += rule->apply_second(tmp);
+ } else {
+ //We use previous available hyphenation
+ if (w1 > 0) line += spaces;
+ output.push_back(line + prev);
+ line.clear();
+ lineWidth = 0;
+ w1 = 0;
+
+ //Rewind
+ prev.clear();
+ prevWidth = 0;
+ skip = prevSkip;
+ i = prevIndex;
+
+ //Update buffer
+ tmp.clear();
+ rule = (*rules)[i];
+ assert(rule != NULL);
+ skip += rule->apply_second(tmp);
+ }
+ } else if (i == m) {
+ //Output last part
+ if (w1 > 0) line += spaces;
+ line += tmp2;
+ lineWidth += w1 + newWidth;
+ } else if (newWidth > prevWidth) {
+ //Update prev hyphenation
+ prev = tmp2;
+ prevSkip = skip;
+ prevWidth = newWidth;
+ prevIndex = i;
+ }
+ }
+
+ if (i >= m) break;
+
+ if (skip > 0) skip--;
+ else tmp.push_back(nonSpaces[i]);
+ }
+}
+
+void WordWrapper::addLine(std::vector<std::string>& output, const std::string& input) {
+ if (!wordWrap) {
+ //Word wrap is not enabled, simply add it to output
+ output.push_back(input);
+ return;
+ }
+
+ const size_t m = input.size();
+
+ std::string spaces, nonSpaces, line;
+ int lineWidth = 0;
+
+ U8STRING_FOR_EACH_CHARACTER_DO_BEGIN(input, i, m, ch, REPLACEMENT_CHARACTER);
+
+ //A word consists of a sequence of white spaces and a sequence of non-white-spaces.
+
+ //TODO: For CJK should only read one CJK character (possibly with a punctuation mark)
+
+ if (utf32IsSpace(ch)) {
+ if (!nonSpaces.empty()) {
+ addWord(output, line, lineWidth, spaces, nonSpaces);
+ spaces.clear();
+ nonSpaces.clear();
+ }
+ U8_ENCODE(ch, spaces.push_back);
+ } else {
+ U8_ENCODE(ch, nonSpaces.push_back);
+ }
+
+ U8STRING_FOR_EACH_CHARACTER_DO_END();
+
+ //FIXME: Here we temporarily ignore trailing spaces
+ if (!nonSpaces.empty()) {
+ addWord(output, line, lineWidth, spaces, nonSpaces);
+ }
+
+ //Output the remaining text.
+ output.push_back(line);
+}
+
+void WordWrapper::addLines(std::vector<std::string>& output, const std::vector<std::string>& input) {
+ for (const std::string& s : input) {
+ addLine(output, s);
+ }
+}
diff --git a/src/WordWrapper.h b/src/WordWrapper.h
new file mode 100644
index 0000000..695cd75
--- /dev/null
+++ b/src/WordWrapper.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 Me and My Shadow
+ *
+ * This file is part of Me and My Shadow.
+ *
+ * Me and My Shadow 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WORDWRAPPER_H
+#define WORDWRAPPER_H
+
+#include <vector>
+#include <string>
+#include <set>
+
+// The forward declaration of TTF_Font is clunky
+struct _TTF_Font;
+typedef struct _TTF_Font TTF_Font;
+
+class WordWrapper {
+public:
+ // The font.
+ TTF_Font *font;
+
+ // The maximal width (in pixels if font is not NULL, in characters if font is NULL)
+ int maxWidth;
+
+ // The hyphen used for the hyphenator. If it's empty, the hyphenator is disabled.
+ std::string hyphen;
+
+ // Enable word wrapping.
+ bool wordWrap;
+
+ // Don't hyphenate the words containing http:// or https://.
+ bool reserveHyperlinks;
+
+ // Don't hyphenate the following reserved words (case sensitive).
+ std::set<std::string> reservedWords;
+
+ // The language used for the hyphenator.
+ // "" means use current language.
+ std::string hyphenatorLanguage;
+
+private:
+ // Calculate text width (in pixels if font is not NULL, in characters if font is NULL).
+ int getTextWidth(const std::string& s);
+
+ // Calculate glyph width (in pixels if font is not NULL, in characters if font is NULL).
+ int getGlyphWidth(int ch);
+
+ // Check if a word is reserved.
+ bool isReserved(const std::string& word);
+
+ // Internal function.
+ void addWord(std::vector<std::string>& output, std::string& line, int& lineWidth, const std::string& spaces, const std::string& nonSpaces);
+
+public:
+ WordWrapper();
+ ~WordWrapper();
+
+ // Add a string (possible multiline) to output.
+ void addString(std::vector<std::string>& output, const std::string& input);
+
+ // Add a single line string (subject to wordwrap) to output.
+ void addLine(std::vector<std::string>& output, const std::string& input);
+
+ // Add a list of lines (assume each line is a single line string, subject to wordwrap) to output.
+ void addLines(std::vector<std::string>& output, const std::vector<std::string>& input);
+};
+
+#endif

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 9, 8:01 PM (6 d, 22 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
62832
Default Alt Text
(10 KB)

Event Timeline