Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F116835
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
10 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
R79 meandmyshadow
Attached
Detach File
Event Timeline