├── .gitignore ├── BidiRenderer.pro ├── LICENSE ├── README.md ├── arrow.cpp ├── arrow.h ├── colorpicker.cpp ├── colorpicker.h ├── fonts ├── DejaVuSans.ttf └── license.txt ├── glyph_string.cpp ├── glyph_string.h ├── glyph_string_renderer.cpp ├── glyph_string_renderer.h ├── glyph_string_visualizer.cpp ├── glyph_string_visualizer.h ├── graphicsview.cpp ├── graphicsview.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── resource.qrc ├── samples.txt └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | -------------------------------------------------------------------------------- /BidiRenderer.pro: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Automatically generated by qmake (3.0) Tue Feb 17 23:34:25 2015 3 | ###################################################################### 4 | 5 | FREETYPE_INCLUDEPATH = /usr/include/freetype2 6 | FREETYPE_LIBS = -L/usr/lib/x86_64-linux-gnu -lfreetype -lz -lpng12 7 | 8 | HARFBUZZ_INCLUDEPATH = /usr/include/harfbuzz 9 | HARFBUZZ_LIBS = -lharfbuzz 10 | 11 | FRIBIDI_INCLUDEPATH = /usr/include/fribidi 12 | FRIBIDI_LIBS = -lfribidi 13 | 14 | QT += core widgets 15 | TEMPLATE = app 16 | TARGET = BidiRenderer 17 | INCLUDEPATH += . $${FREETYPE_INCLUDEPATH} $${HARFBUZZ_INCLUDEPATH} \ 18 | $${FRIBIDI_INCLUDEPATH} 19 | LIBS += $${HARFBUZZ_LIBS} $${FRIBIDI_LIBS} $${FREETYPE_LIBS} 20 | 21 | # Input 22 | HEADERS += glyph_string.h glyph_string_visualizer.h arrow.h glyph_string_renderer.h \ 23 | graphicsview.h \ 24 | mainwindow.h \ 25 | colorpicker.h 26 | SOURCES += glyph_string.cpp glyph_string_visualizer.cpp main.cpp arrow.cpp glyph_string_renderer.cpp \ 27 | graphicsview.cpp \ 28 | mainwindow.cpp \ 29 | colorpicker.cpp 30 | 31 | FORMS += \ 32 | mainwindow.ui 33 | 34 | RESOURCES += \ 35 | resource.qrc 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Salah-Eddin Shaban 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 19 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | BidiRenderer: 3 | 4 | This is a test application I'm using to help me understand the Unicode bidirectional algorithm, and the process of rendering complex scripts and RTL text. It's the basis for a more complex version of the code that has been used in VLC's FreeType module for subtitles/OSD rendering. 5 | 6 | Some options do not produce visible effects on all fonts. For example removing zero-width characters when the font can already handle them properly. The font Arial is good here since it shows zero-width spaces, Unicode control characters, etc. 7 | 8 | See the wiki for build instructions on Windows and Linux/Unix. 9 | 10 | I welcome any comments or corrections, particularly regarding language-specific issues. 11 | 12 | ![alt tag](https://github.com/salshaaban/BidiRenderer/blob/master/screenshot.png) 13 | -------------------------------------------------------------------------------- /arrow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include "arrow.h" 29 | 30 | const float Arrow::Sin45 = 0.707106781f; 31 | const float Arrow::Cos45 = 0.707106781f; 32 | 33 | Arrow::Arrow() : mPenWidth(0), mArrowSize(0), mHeight(0), 34 | mX1(0), mX2(0), mY(0), mRtl(false) 35 | { 36 | } 37 | 38 | Arrow::Arrow(const QFont &font, const QString &text, int penWidth, 39 | int x1, int x2, int y, bool rtl, QColor color) 40 | { 41 | init(font, text, penWidth, x1, x2, y, rtl, color); 42 | } 43 | 44 | void Arrow::init(const QFont &font, const QString &text, int penWidth, 45 | int x1, int x2, int y, bool rtl, QColor color) 46 | { 47 | mFont = font; 48 | mText = text; 49 | mPenWidth = penWidth; 50 | if (x2 - x1 >= 2 * penWidth) { 51 | x1 += penWidth; 52 | x2 -= penWidth; 53 | } 54 | mY = y; 55 | mColor = color; 56 | 57 | mArrowSize = 2 * mPenWidth; 58 | QFontMetrics fontMetrics(mFont); 59 | 60 | mHeight = mArrowSize > fontMetrics.height() ? 61 | 2 * mPenWidth + 2 * mArrowSize: 62 | 2 * mPenWidth + fontMetrics.height() + mArrowSize; 63 | 64 | mY = mY + mHeight / 2; 65 | QPoint p1; 66 | QPoint p2; 67 | if (rtl) { 68 | mX1 = x2; 69 | mX2 = x1; 70 | p1.setX(mX2 + (mArrowSize * Cos45)); 71 | p1.setY(mY + (mArrowSize * Sin45)); 72 | p2.setX(mX2 + (mArrowSize * Cos45)); 73 | p2.setY(mY - (mArrowSize * Sin45)); 74 | } else { 75 | mX1 = x1; 76 | mX2 = x2; 77 | p1.setX(mX2 - (mArrowSize * Cos45)); 78 | p1.setY(mY + (mArrowSize * Sin45)); 79 | p2.setX(mX2 - (mArrowSize * Cos45)); 80 | p2.setY(mY - (mArrowSize * Sin45)); 81 | } 82 | mLine = QLine(mX1, mY, mX2, mY); 83 | 84 | mArrowHead << QPoint(mX2, mY) << p1 << p2; 85 | mTextPosition.setX(mX1 + (mX2 - mX1 - fontMetrics.width(text)) / 2); 86 | mTextPosition.setY(mY - mPenWidth); 87 | } 88 | 89 | void Arrow::paint(QPainter *painter) 90 | { 91 | QPen pen; 92 | pen.setWidth(mPenWidth); 93 | pen.setJoinStyle(Qt::RoundJoin); 94 | pen.setCapStyle(Qt::RoundCap); 95 | pen.setColor(mColor); 96 | 97 | painter->save(); 98 | 99 | painter->setPen(pen); 100 | painter->setBrush(mColor); 101 | painter->setFont(mFont); 102 | 103 | painter->drawLine(mLine); 104 | painter->drawPolygon(mArrowHead); 105 | painter->drawText(mTextPosition.x(), mTextPosition.y(), mText); 106 | 107 | painter->restore(); 108 | } 109 | -------------------------------------------------------------------------------- /arrow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | class QPainter; 34 | 35 | class Arrow 36 | { 37 | public: 38 | static const float Sin45; 39 | static const float Cos45; 40 | 41 | Arrow(); 42 | Arrow(const QFont &font, const QString &text, 43 | int penWidth, int x1, int x2, int y, bool rtl, QColor color = Qt::black); 44 | void init(const QFont &font, const QString &text, 45 | int penWidth, int x1, int x2, int y, bool rtl, QColor color = Qt::black); 46 | void paint(QPainter *painter); 47 | int height() const { return mHeight; } 48 | 49 | protected: 50 | QFont mFont; 51 | QString mText; 52 | QPoint mTextPosition; 53 | int mPenWidth; 54 | int mArrowSize; 55 | int mHeight; 56 | int mX1; 57 | int mX2; 58 | int mY; 59 | bool mRtl; 60 | QLine mLine; 61 | QPolygon mArrowHead; 62 | QColor mColor; 63 | }; 64 | -------------------------------------------------------------------------------- /colorpicker.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include "colorpicker.h" 26 | 27 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 28 | #include 29 | #else 30 | #include 31 | #endif 32 | 33 | ColorPicker::ColorPicker(QWidget *parent) : QLabel(parent), mColor(Qt::black) 34 | { 35 | updateStyleSheet(); 36 | } 37 | 38 | ColorPicker::~ColorPicker() 39 | { 40 | 41 | } 42 | 43 | void ColorPicker::mouseReleaseEvent(QMouseEvent *) 44 | { 45 | QColor color = QColorDialog::getColor(mColor, this); 46 | if (color.isValid()) { 47 | mColor = color; 48 | updateStyleSheet(); 49 | } 50 | } 51 | 52 | void ColorPicker::updateStyleSheet() 53 | { 54 | QString styleSheetText = QString("background-color: rgb(%1, %2, %3)") 55 | .arg(mColor.red()).arg(mColor.green()).arg(mColor.blue()); 56 | setStyleSheet(styleSheetText); 57 | emit colorChanged(mColor); 58 | } 59 | 60 | void ColorPicker::setColor(QColor color) 61 | { 62 | mColor = color; 63 | updateStyleSheet(); 64 | } 65 | 66 | QColor ColorPicker::color() const 67 | { 68 | return mColor; 69 | } 70 | -------------------------------------------------------------------------------- /colorpicker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #ifndef COLORPICKER_H 26 | #define COLORPICKER_H 27 | 28 | #include 29 | 30 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 31 | #include 32 | #else 33 | #include 34 | #endif 35 | 36 | class ColorPicker : public QLabel 37 | { 38 | Q_OBJECT 39 | public: 40 | ColorPicker(QWidget *parent = 0); 41 | ~ColorPicker(); 42 | void mouseReleaseEvent(QMouseEvent *event); 43 | void setColor(QColor color); 44 | QColor color() const; 45 | signals: 46 | void colorChanged(QColor color); 47 | protected: 48 | QColor mColor; 49 | void updateStyleSheet(); 50 | }; 51 | 52 | #endif // COLORPICKER_H 53 | -------------------------------------------------------------------------------- /fonts/DejaVuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salshaaban/BidiRenderer/40eaf873cd116d6e911ec1decccb1f508f13181f/fonts/DejaVuSans.ttf -------------------------------------------------------------------------------- /fonts/license.txt: -------------------------------------------------------------------------------- 1 | License 2 | Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on Gnome page on Bitstream Vera fonts. Glyphs imported from Arev fonts are © Tavmjung Bah (see below) 3 | 4 | Bitstream Vera Fonts Copyright 5 | 6 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: 8 | The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. 9 | The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". 10 | This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. 11 | The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. 12 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. 13 | Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. 14 | 15 | Arev Fonts Copyright 16 | 17 | Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. 18 | Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: 19 | The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. 20 | The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev". 21 | This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names. 22 | The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. 23 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. 24 | Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr. 25 | 26 | -------------------------------------------------------------------------------- /glyph_string.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include "glyph_string.h" 29 | 30 | QString scriptShortText(hb_script_t script) 31 | { 32 | char buffer[5] = {0}; 33 | hb_tag_to_string(script, buffer); 34 | return QString(buffer); 35 | } 36 | 37 | QString scriptLongText(hb_script_t script) 38 | { 39 | switch (script) { 40 | case HB_SCRIPT_COMMON: return QString("HB_SCRIPT_COMMON"); 41 | case HB_SCRIPT_INHERITED: return QString("HB_SCRIPT_INHERITED"); 42 | case HB_SCRIPT_UNKNOWN: return QString("HB_SCRIPT_UNKNOWN"); 43 | case HB_SCRIPT_ARABIC: return QString("HB_SCRIPT_ARABIC"); 44 | case HB_SCRIPT_ARMENIAN: return QString("HB_SCRIPT_ARMENIAN"); 45 | case HB_SCRIPT_BENGALI: return QString("HB_SCRIPT_BENGALI"); 46 | case HB_SCRIPT_CYRILLIC: return QString("HB_SCRIPT_CYRILLIC"); 47 | case HB_SCRIPT_DEVANAGARI: return QString("HB_SCRIPT_DEVANAGARI"); 48 | case HB_SCRIPT_GEORGIAN: return QString("HB_SCRIPT_GEORGIAN"); 49 | case HB_SCRIPT_GREEK: return QString("HB_SCRIPT_GREEK"); 50 | case HB_SCRIPT_GUJARATI: return QString("HB_SCRIPT_GUJARATI"); 51 | case HB_SCRIPT_GURMUKHI: return QString("HB_SCRIPT_GURMUKHI"); 52 | case HB_SCRIPT_HANGUL: return QString("HB_SCRIPT_HANGUL"); 53 | case HB_SCRIPT_HAN: return QString("HB_SCRIPT_HAN"); 54 | case HB_SCRIPT_HEBREW: return QString("HB_SCRIPT_HEBREW"); 55 | case HB_SCRIPT_HIRAGANA: return QString("HB_SCRIPT_HIRAGANA"); 56 | case HB_SCRIPT_KANNADA: return QString("HB_SCRIPT_KANNADA"); 57 | case HB_SCRIPT_KATAKANA: return QString("HB_SCRIPT_KATAKANA"); 58 | case HB_SCRIPT_LAO: return QString("HB_SCRIPT_LAO"); 59 | case HB_SCRIPT_LATIN: return QString("HB_SCRIPT_LATIN"); 60 | case HB_SCRIPT_MALAYALAM: return QString("HB_SCRIPT_MALAYALAM"); 61 | case HB_SCRIPT_ORIYA: return QString("HB_SCRIPT_ORIYA"); 62 | case HB_SCRIPT_TAMIL: return QString("HB_SCRIPT_TAMIL"); 63 | case HB_SCRIPT_TELUGU: return QString("HB_SCRIPT_TELUGU"); 64 | case HB_SCRIPT_THAI: return QString("HB_SCRIPT_THAI"); 65 | case HB_SCRIPT_TIBETAN: return QString("HB_SCRIPT_TIBETAN"); 66 | case HB_SCRIPT_BOPOMOFO: return QString("HB_SCRIPT_BOPOMOFO"); 67 | case HB_SCRIPT_BRAILLE: return QString("HB_SCRIPT_BRAILLE"); 68 | case HB_SCRIPT_CANADIAN_SYLLABICS: return QString("HB_SCRIPT_CANADIAN_SYLLABICS"); 69 | case HB_SCRIPT_CHEROKEE: return QString("HB_SCRIPT_CHEROKEE"); 70 | case HB_SCRIPT_ETHIOPIC: return QString("HB_SCRIPT_ETHIOPIC"); 71 | case HB_SCRIPT_KHMER: return QString("HB_SCRIPT_KHMER"); 72 | case HB_SCRIPT_MONGOLIAN: return QString("HB_SCRIPT_MONGOLIAN"); 73 | case HB_SCRIPT_MYANMAR: return QString("HB_SCRIPT_MYANMAR"); 74 | case HB_SCRIPT_OGHAM: return QString("HB_SCRIPT_OGHAM"); 75 | case HB_SCRIPT_RUNIC: return QString("HB_SCRIPT_RUNIC"); 76 | case HB_SCRIPT_SINHALA: return QString("HB_SCRIPT_SINHALA"); 77 | case HB_SCRIPT_SYRIAC: return QString("HB_SCRIPT_SYRIAC"); 78 | case HB_SCRIPT_THAANA: return QString("HB_SCRIPT_THAANA"); 79 | case HB_SCRIPT_YI: return QString("HB_SCRIPT_YI"); 80 | //case HB_SCRIPT_DESERT: return QString("HB_SCRIPT_DESERT"); 81 | case HB_SCRIPT_GOTHIC: return QString("HB_SCRIPT_GOTHIC"); 82 | case HB_SCRIPT_OLD_ITALIC: return QString("HB_SCRIPT_OLD_ITALIC"); 83 | case HB_SCRIPT_BUHID: return QString("HB_SCRIPT_BUHID"); 84 | case HB_SCRIPT_HANUNOO: return QString("HB_SCRIPT_HANUNOO"); 85 | case HB_SCRIPT_TAGALOG: return QString("HB_SCRIPT_TAGALOG"); 86 | case HB_SCRIPT_TAGBANWA: return QString("HB_SCRIPT_TAGBANWA"); 87 | case HB_SCRIPT_CYPRIOT: return QString("HB_SCRIPT_CYPRIOT"); 88 | case HB_SCRIPT_LIMBU: return QString("HB_SCRIPT_LIMBU"); 89 | case HB_SCRIPT_LINEAR_B: return QString("HB_SCRIPT_LINEAR_B"); 90 | case HB_SCRIPT_OSMANYA: return QString("HB_SCRIPT_OSMANYA"); 91 | case HB_SCRIPT_SHAVIAN: return QString("HB_SCRIPT_SHAVIAN"); 92 | case HB_SCRIPT_TAI_LE: return QString("HB_SCRIPT_TAI_LE"); 93 | case HB_SCRIPT_UGARITIC: return QString("HB_SCRIPT_UGARITIC"); 94 | case HB_SCRIPT_BUGINESE: return QString("HB_SCRIPT_BUGINESE"); 95 | case HB_SCRIPT_COPTIC: return QString("HB_SCRIPT_COPTIC"); 96 | case HB_SCRIPT_GLAGOLITIC: return QString("HB_SCRIPT_GLAGOLITIC"); 97 | case HB_SCRIPT_KHAROSHTHI: return QString("HB_SCRIPT_KHAROSHTHI"); 98 | case HB_SCRIPT_NEW_TAI_LUE: return QString("HB_SCRIPT_NEW_TAI_LUE"); 99 | case HB_SCRIPT_OLD_PERSIAN: return QString("HB_SCRIPT_OLD_PERSIAN"); 100 | case HB_SCRIPT_SYLOTI_NAGRI: return QString("HB_SCRIPT_SYLOTI_NAGRI"); 101 | case HB_SCRIPT_TIFINAGH: return QString("HB_SCRIPT_TIFINAGH"); 102 | case HB_SCRIPT_BALINESE: return QString("HB_SCRIPT_BALINESE"); 103 | case HB_SCRIPT_CUNEIFORM: return QString("HB_SCRIPT_CUNEIFORM"); 104 | case HB_SCRIPT_NKO: return QString("HB_SCRIPT_NKO"); 105 | case HB_SCRIPT_PHAGS_PA: return QString("HB_SCRIPT_PHAGS_PA"); 106 | case HB_SCRIPT_PHOENICIAN: return QString("HB_SCRIPT_PHOENICIAN"); 107 | case HB_SCRIPT_CARIAN: return QString("HB_SCRIPT_CARIAN"); 108 | case HB_SCRIPT_CHAM: return QString("HB_SCRIPT_CHAM"); 109 | case HB_SCRIPT_KAYAH_LI: return QString("HB_SCRIPT_KAYAH_LI"); 110 | case HB_SCRIPT_LEPCHA: return QString("HB_SCRIPT_LEPCHA"); 111 | case HB_SCRIPT_LYCIAN: return QString("HB_SCRIPT_LYCIAN"); 112 | case HB_SCRIPT_LYDIAN: return QString("HB_SCRIPT_LYDIAN"); 113 | case HB_SCRIPT_OL_CHIKI: return QString("HB_SCRIPT_OL_CHIKI"); 114 | case HB_SCRIPT_REJANG: return QString("HB_SCRIPT_REJANG"); 115 | case HB_SCRIPT_SAURASHTRA: return QString("HB_SCRIPT_SAURASHTRA"); 116 | case HB_SCRIPT_SUNDANESE: return QString("HB_SCRIPT_SUNDANESE"); 117 | case HB_SCRIPT_VAI: return QString("HB_SCRIPT_VAI"); 118 | case HB_SCRIPT_AVESTAN: return QString("HB_SCRIPT_AVESTAN"); 119 | case HB_SCRIPT_BAMUM: return QString("HB_SCRIPT_BAMUM"); 120 | case HB_SCRIPT_EGYPTIAN_HIEROGLYPHS: return QString("HB_SCRIPT_EGYPTIAN_HIEROGLYPHS"); 121 | case HB_SCRIPT_IMPERIAL_ARAMAIC: return QString("HB_SCRIPT_IMPERIAL_ARAMAIC"); 122 | case HB_SCRIPT_INSCRIPTIONAL_PAHLAVI: return QString("HB_SCRIPT_INSCRIPTIONAL_PAHLAVI"); 123 | case HB_SCRIPT_INSCRIPTIONAL_PARTHIAN: return QString("HB_SCRIPT_INSCRIPTIONAL_PARTHIAN"); 124 | case HB_SCRIPT_JAVANESE: return QString("HB_SCRIPT_JAVANESE"); 125 | case HB_SCRIPT_KAITHI: return QString("HB_SCRIPT_KAITHI"); 126 | case HB_SCRIPT_LISU: return QString("HB_SCRIPT_LISU"); 127 | case HB_SCRIPT_MEETEI_MAYEK: return QString("HB_SCRIPT_MEETEI_MAYEK"); 128 | case HB_SCRIPT_OLD_SOUTH_ARABIAN: return QString("HB_SCRIPT_OLD_SOUTH_ARABIAN"); 129 | case HB_SCRIPT_OLD_TURKIC: return QString("HB_SCRIPT_OLD_TURKIC"); 130 | case HB_SCRIPT_SAMARITAN: return QString("HB_SCRIPT_SAMARITAN"); 131 | case HB_SCRIPT_TAI_THAM: return QString("HB_SCRIPT_TAI_THAM"); 132 | case HB_SCRIPT_TAI_VIET: return QString("HB_SCRIPT_TAI_VIET"); 133 | case HB_SCRIPT_BATAK: return QString("HB_SCRIPT_BATAK"); 134 | case HB_SCRIPT_BRAHMI: return QString("HB_SCRIPT_BRAHMI"); 135 | case HB_SCRIPT_MANDAIC: return QString("HB_SCRIPT_MANDAIC"); 136 | case HB_SCRIPT_CHAKMA: return QString("HB_SCRIPT_CHAKMA"); 137 | case HB_SCRIPT_MEROITIC_CURSIVE: return QString("HB_SCRIPT_MEROITIC_CURSIVE"); 138 | case HB_SCRIPT_MEROITIC_HIEROGLYPHS: return QString("HB_SCRIPT_MEROITIC_HIEROGLYPHS"); 139 | case HB_SCRIPT_MIAO: return QString("HB_SCRIPT_MIAO"); 140 | case HB_SCRIPT_SHARADA: return QString("HB_SCRIPT_SHARADA"); 141 | case HB_SCRIPT_SORA_SOMPENG: return QString("HB_SCRIPT_SORA_SOMPENG"); 142 | case HB_SCRIPT_TAKRI: return QString("HB_SCRIPT_TAKRI"); 143 | //case HB_SCRIPT_BASSA_VAH: return QString("HB_SCRIPT_BASSA_VAH"); 144 | //case HB_SCRIPT_CAUCASIAN_ALBANIAN: return QString("HB_SCRIPT_CAUCASIAN_ALBANIAN"); 145 | //case HB_SCRIPT_DUPLOYAN: return QString("HB_SCRIPT_DUPLOYAN"); 146 | //case HB_SCRIPT_ELBASAN: return QString("HB_SCRIPT_ELBASAN"); 147 | //case HB_SCRIPT_GRANTHA: return QString("HB_SCRIPT_GRANTHA"); 148 | //case HB_SCRIPT_KHOJKI: return QString("HB_SCRIPT_KHOJKI"); 149 | //case HB_SCRIPT_KHUDAWADI: return QString("HB_SCRIPT_KHUDAWADI"); 150 | //case HB_SCRIPT_LINEAR_A: return QString("HB_SCRIPT_LINEAR_A"); 151 | //case HB_SCRIPT_MAHAJANI: return QString("HB_SCRIPT_MAHAJANI"); 152 | //case HB_SCRIPT_MANICHAEAN: return QString("HB_SCRIPT_MANICHAEAN"); 153 | //case HB_SCRIPT_MENDE_KIKAKUI: return QString("HB_SCRIPT_MENDE_KIKAKUI"); 154 | //case HB_SCRIPT_MODI: return QString("HB_SCRIPT_MODI"); 155 | //case HB_SCRIPT_MRO: return QString("HB_SCRIPT_MRO"); 156 | //case HB_SCRIPT_NABATAEAN: return QString("HB_SCRIPT_NABATAEAN"); 157 | //case HB_SCRIPT_OLD_NORTH_ARABIAN: return QString("HB_SCRIPT_OLD_NORTH_ARABIAN"); 158 | //case HB_SCRIPT_OLD_PERMIC: return QString("HB_SCRIPT_OLD_PERMIC"); 159 | //case HB_SCRIPT_PAHAWH_HMONG: return QString("HB_SCRIPT_PAHAWH_HMONG"); 160 | //case HB_SCRIPT_PALMYRENE: return QString("HB_SCRIPT_PALMYRENE"); 161 | //case HB_SCRIPT_PAU_CIN_HAU: return QString("HB_SCRIPT_PAU_CIN_HAU"); 162 | //case HB_SCRIPT_PSALTER_PAHLAVI: return QString("HB_SCRIPT_PSALTER_PAHLAVI"); 163 | //case HB_SCRIPT_SIDDHAM: return QString("HB_SCRIPT_SIDDHAM"); 164 | //case HB_SCRIPT_TIRHUTA: return QString("HB_SCRIPT_TIRHUTA"); 165 | //case HB_SCRIPT_WARANG_CITI: return QString("HB_SCRIPT_WARANG_CITI"); 166 | case HB_SCRIPT_INVALID: return QString("HB_SCRIPT_INVALID"); 167 | default: return QString("HB_SCRIPT_INVALID"); 168 | } 169 | } 170 | 171 | QString charTypeShortText(FriBidiCharType charType) 172 | { 173 | switch (charType) { 174 | case FRIBIDI_TYPE_LTR: return QString("LTR"); 175 | case FRIBIDI_TYPE_RTL: return QString("RTL"); 176 | case FRIBIDI_TYPE_AL: return QString("AL"); 177 | case FRIBIDI_TYPE_EN: return QString("EN"); 178 | case FRIBIDI_TYPE_AN: return QString("AN"); 179 | case FRIBIDI_TYPE_ES: return QString("ES"); 180 | case FRIBIDI_TYPE_ET: return QString("ET"); 181 | case FRIBIDI_TYPE_CS: return QString("CS"); 182 | case FRIBIDI_TYPE_NSM: return QString("NSM"); 183 | case FRIBIDI_TYPE_BN: return QString("BN"); 184 | case FRIBIDI_TYPE_BS: return QString("BS"); 185 | case FRIBIDI_TYPE_SS: return QString("SS"); 186 | case FRIBIDI_TYPE_WS: return QString("WS"); 187 | case FRIBIDI_TYPE_ON: return QString("ON"); 188 | case FRIBIDI_TYPE_LRE: return QString("LRE"); 189 | case FRIBIDI_TYPE_RLE: return QString("RLE"); 190 | case FRIBIDI_TYPE_LRO: return QString("LRO"); 191 | case FRIBIDI_TYPE_RLO: return QString("RLO"); 192 | case FRIBIDI_TYPE_PDF: return QString("PDF"); 193 | } 194 | return QString(); 195 | } 196 | 197 | QString charTypeLongText(FriBidiCharType charType) 198 | { 199 | switch (charType) { 200 | case FRIBIDI_TYPE_LTR: return QString("Left-to-Right Letter"); 201 | case FRIBIDI_TYPE_RTL: return QString("Right-to-Left Letter"); 202 | case FRIBIDI_TYPE_AL: return QString("Arabic Letter"); 203 | case FRIBIDI_TYPE_EN: return QString("European Numeral"); 204 | case FRIBIDI_TYPE_AN: return QString("Arabic Numeral"); 205 | case FRIBIDI_TYPE_ES: return QString("European Number Separator"); 206 | case FRIBIDI_TYPE_ET: return QString("European Number Terminator"); 207 | case FRIBIDI_TYPE_CS: return QString("Common Separator"); 208 | case FRIBIDI_TYPE_NSM: return QString("Non Spacing Mark"); 209 | case FRIBIDI_TYPE_BN: return QString("Boundary Neutral"); 210 | case FRIBIDI_TYPE_BS: return QString("Block Separator"); 211 | case FRIBIDI_TYPE_SS: return QString("Segment Separator"); 212 | case FRIBIDI_TYPE_WS: return QString("White Space"); 213 | case FRIBIDI_TYPE_ON: return QString("Other Neutral"); 214 | case FRIBIDI_TYPE_LRE: return QString("Left-to-Right Embedding"); 215 | case FRIBIDI_TYPE_RLE: return QString("Right-to-Left Embedding"); 216 | case FRIBIDI_TYPE_LRO: return QString("Left-to-Right Override"); 217 | case FRIBIDI_TYPE_RLO: return QString("Right-to-Left Override"); 218 | case FRIBIDI_TYPE_PDF: return QString("Pop Directional Flag"); 219 | } 220 | return QString(); 221 | } 222 | 223 | GlyphString::GlyphString() : 224 | mCodePoints(0), mGlyphIndices(0), mImages(0), mGeometries(0), 225 | mRuns(0), mLines(0), mScripts(0), mTypes(0), mLevels(0), mMap(0), 226 | mState(Invalid) 227 | { 228 | } 229 | 230 | GlyphString::~GlyphString() 231 | { 232 | delete[] mMap; 233 | delete[] mLevels; 234 | delete[] mTypes; 235 | delete[] mScripts; 236 | delete[] mLines; 237 | delete[] mRuns; 238 | delete[] mGeometries; 239 | delete[] mImages; 240 | delete[] mGlyphIndices; 241 | delete[] mCodePoints; 242 | } 243 | 244 | bool GlyphString::init(quint32 *codePoints, int size, FT_Face face, 245 | QColor faceColor, FriBidiParType parType, int maxWidth) 246 | { 247 | if (size <= 0) return false; 248 | 249 | mCodePoints = new quint32[size]; 250 | memset(mCodePoints, 0, size * sizeof(*mCodePoints)); 251 | 252 | mGlyphIndices = new int[size]; 253 | memset(mGlyphIndices, 0, size * sizeof(*mGlyphIndices)); 254 | 255 | mImages = new QImage[size]; 256 | mGeometries = new Geometry[size]; 257 | 258 | mRuns = new int[size]; 259 | memset(mRuns, 0, size * sizeof(*mRuns)); 260 | 261 | mLines = new int[size]; 262 | memset(mLines, 0, size * sizeof(*mLines)); 263 | 264 | mScripts = new hb_script_t[size]; 265 | memset(mScripts, 0, size * sizeof(*mScripts)); 266 | 267 | mTypes = new FriBidiCharType[size]; 268 | memset(mTypes, 0, size * sizeof(*mTypes)); 269 | 270 | mLevels = new FriBidiLevel[size]; 271 | memset(mLevels, 0, size * sizeof(*mLevels)); 272 | 273 | mMap = new FriBidiStrIndex[size]; 274 | memset(mMap, 0, size * sizeof(*mMap)); 275 | 276 | memcpy(mCodePoints, codePoints, size * sizeof(*codePoints)); 277 | mSize = size; 278 | mFace = face; 279 | mFaceColor = faceColor; 280 | mMaxWidth = maxWidth; 281 | mParType = parType; 282 | 283 | loadGlyphImages(); 284 | 285 | mState = Initialized; 286 | return true; 287 | } 288 | 289 | bool GlyphString::analyze(bool resolveScripts, bool breakOnLevelChange) 290 | { 291 | if (mState != Initialized) 292 | return false; 293 | 294 | fribidi_get_bidi_types(mCodePoints, mSize, mTypes); 295 | fribidi_get_par_embedding_levels(mTypes, mSize, &mParType, mLevels); 296 | 297 | hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default(); 298 | for (int i = 0; i < mSize; ++i) 299 | mScripts[i] = hb_unicode_script(ufuncs, mCodePoints[i]); 300 | 301 | if (resolveScripts) { 302 | hb_script_t lastScriptValue; 303 | int lastScriptIndex = -1; 304 | int lastSetIndex = -1; 305 | 306 | for (int i = 0; i < mSize; ++i) { 307 | if (mScripts[i] == HB_SCRIPT_COMMON || mScripts[i] == HB_SCRIPT_INHERITED) { 308 | if (lastScriptIndex != -1) { 309 | mScripts[i] = lastScriptValue; 310 | lastSetIndex = i; 311 | } 312 | } else { 313 | for (int j = lastSetIndex + 1; j < i; ++j) 314 | mScripts[j] = mScripts[i]; 315 | lastScriptValue = mScripts[i]; 316 | lastScriptIndex = i; 317 | lastSetIndex = i; 318 | } 319 | } 320 | } 321 | 322 | int runID = 0; 323 | hb_script_t lastScript = mScripts[0]; 324 | int lastLevel = mLevels[0]; 325 | int lastRunStart = 0; 326 | for (int i = 0; i <= mSize; ++i) { 327 | if (i == mSize || mScripts[i] != lastScript || 328 | (breakOnLevelChange && mLevels[i] != lastLevel)) 329 | { 330 | ++runID; 331 | RunInfo run; 332 | run.startOffset = lastRunStart; 333 | run.endOffset = i; 334 | run.script = lastScript; 335 | run.direction = lastLevel & 1 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR; 336 | mRunInfos.push_back(run); 337 | if (i < mSize) { 338 | lastScript = mScripts[i]; 339 | lastLevel = mLevels[i]; 340 | lastRunStart = i; 341 | } else { 342 | break; 343 | } 344 | } 345 | mRuns[i] = runID; 346 | } 347 | 348 | mState = Analyzed; 349 | return true; 350 | } 351 | 352 | bool GlyphString::shapeHarfBuzz() 353 | { 354 | if (mState != Analyzed) 355 | return false; 356 | 357 | hb_font_t *hb_font = hb_ft_font_create(mFace, NULL); 358 | int totalGlyphs = 0; 359 | 360 | for (int i = 0; i < mRunInfos.size(); ++i) { 361 | RunInfo &runInfo = mRunInfos[i]; 362 | runInfo.buffer = hb_buffer_create(); 363 | hb_buffer_set_direction(runInfo.buffer, runInfo.direction); 364 | hb_buffer_set_script(runInfo.buffer, runInfo.script); 365 | hb_buffer_add_utf32(runInfo.buffer, mCodePoints + runInfo.startOffset, 366 | runInfo.endOffset - runInfo.startOffset, 0, 367 | runInfo.endOffset - runInfo.startOffset); 368 | hb_shape(hb_font, runInfo.buffer, NULL, 0); 369 | 370 | runInfo.glyphInfos = hb_buffer_get_glyph_infos(runInfo.buffer, 371 | &runInfo.glyphCount); 372 | runInfo.glyphPositions = hb_buffer_get_glyph_positions(runInfo.buffer, 373 | &runInfo.glyphCount); 374 | totalGlyphs += runInfo.glyphCount; 375 | } 376 | 377 | quint32 *newCodePoints = new quint32[totalGlyphs]; 378 | memset(newCodePoints, 0, totalGlyphs * sizeof(*newCodePoints)); 379 | 380 | int *newGlyphIndices = new int[totalGlyphs]; 381 | memset(newGlyphIndices, 0, totalGlyphs * sizeof(*newGlyphIndices)); 382 | 383 | QImage *newImages = new QImage[totalGlyphs]; 384 | Geometry *newGeometries = new Geometry[totalGlyphs]; 385 | 386 | int *newRuns = new int[totalGlyphs]; 387 | memset(newRuns, 0, totalGlyphs * sizeof(*newRuns)); 388 | 389 | int *newLines = new int[totalGlyphs]; 390 | memset(newLines, 0, totalGlyphs * sizeof(*newLines)); 391 | 392 | hb_script_t *newScripts = new hb_script_t[totalGlyphs]; 393 | memset(newScripts, 0, totalGlyphs * sizeof(*newScripts)); 394 | 395 | FriBidiCharType *newTypes = new FriBidiCharType[totalGlyphs]; 396 | memset(newTypes, 0, totalGlyphs * sizeof(*newTypes)); 397 | 398 | FriBidiLevel *newLevels = new FriBidiLevel[totalGlyphs]; 399 | memset(newLevels, 0, totalGlyphs * sizeof(*newLevels)); 400 | 401 | FriBidiStrIndex *newMap = new FriBidiStrIndex[totalGlyphs]; 402 | memset(newMap, 0, totalGlyphs * sizeof(*newMap)); 403 | 404 | int index = 0; 405 | for (int i = 0; i < mRunInfos.size(); ++i) { 406 | RunInfo &runInfo = mRunInfos[i]; 407 | hb_glyph_info_t *glyphInfos = runInfo.glyphInfos; 408 | hb_glyph_position_t *glyphPositions = runInfo.glyphPositions; 409 | for (unsigned int j = 0; j < runInfo.glyphCount; ++j) { 410 | int runIndex = runInfo.direction == HB_DIRECTION_LTR ? 411 | j : runInfo.glyphCount - 1 - j; 412 | int sourceIndex = glyphInfos[runIndex].cluster 413 | + runInfo.startOffset; 414 | //newCodePoints[index] = mCodePoints[sourceIndex]; 415 | newGlyphIndices[index] = glyphInfos[runIndex].codepoint; 416 | newRuns[index] = mRuns[sourceIndex]; 417 | newScripts[index] = mScripts[sourceIndex]; 418 | newTypes[index] = mTypes[sourceIndex]; 419 | newLevels[index] = mLevels[sourceIndex]; 420 | newGeometries[index].xOffset = glyphPositions[runIndex].x_offset / 64; 421 | newGeometries[index].yOffset = glyphPositions[runIndex].y_offset / 64; 422 | newGeometries[index].xAdvance = glyphPositions[runIndex].x_advance / 64; 423 | newGeometries[index].yAdvance = glyphPositions[runIndex].y_advance / 64; 424 | ++index; 425 | } 426 | } 427 | 428 | delete[] mCodePoints; 429 | delete[] mGlyphIndices; 430 | delete[] mImages; 431 | delete[] mGeometries; 432 | delete[] mRuns; 433 | delete[] mLines; 434 | delete[] mScripts; 435 | delete[] mTypes; 436 | delete[] mLevels; 437 | delete[] mMap; 438 | 439 | mCodePoints = newCodePoints; 440 | mGlyphIndices = newGlyphIndices; 441 | mImages = newImages; 442 | mGeometries = newGeometries; 443 | mRuns = newRuns; 444 | mLines = newLines; 445 | mScripts = newScripts; 446 | mTypes = newTypes; 447 | mLevels = newLevels; 448 | mMap = newMap; 449 | mSize = totalGlyphs; 450 | 451 | loadGlyphImages(true, true); 452 | 453 | hb_font_destroy(hb_font); 454 | for (int i = 0; i < mRunInfos.size(); ++i) { 455 | hb_buffer_destroy(mRunInfos[i].buffer); 456 | mRunInfos[i].buffer = 0; 457 | mRunInfos[i].glyphInfos = 0; 458 | mRunInfos[i].glyphPositions = 0; 459 | mRunInfos[i].glyphCount = 0; 460 | } 461 | 462 | mState = Shaped; 463 | return true; 464 | } 465 | 466 | bool GlyphString::shapeFriBidi(bool removeInvisibleCharacters) 467 | { 468 | if (mState != Analyzed) 469 | return false; 470 | 471 | FriBidiJoiningType *joiningTypes = new FriBidiJoiningType[mSize]; 472 | fribidi_get_joining_types(mCodePoints, mSize, joiningTypes); 473 | fribidi_join_arabic(mTypes, mSize, mLevels, joiningTypes); 474 | fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, 475 | mLevels, mSize, joiningTypes, mCodePoints); 476 | delete[] joiningTypes; 477 | loadGlyphImages(); 478 | 479 | if (removeInvisibleCharacters) { 480 | for (int i = 0; i < mSize; ++i) { 481 | if (mCodePoints[i] == 0xfeff 482 | || mCodePoints[i] == 0x061c 483 | || (mCodePoints[i] >= 0x202a && mCodePoints[i] <= 0x202e) 484 | || (mCodePoints[i] >= 0x2060 && mCodePoints[i] <= 0x2069) 485 | || (mCodePoints[i] >= 0x200b && mCodePoints[i] <= 0x200f)) 486 | clearGlyph(i); 487 | } 488 | } 489 | 490 | mState = Shaped; 491 | return true; 492 | } 493 | 494 | bool GlyphString::reorderLine(int startOffset, int endOffset) 495 | { 496 | int length = endOffset - startOffset; 497 | fribidi_reorder_line(0, mTypes + startOffset, length, 0, mParType, 498 | mLevels + startOffset, 0, mMap + startOffset); 499 | return true; 500 | } 501 | 502 | bool GlyphString::layout() 503 | { 504 | if (mState == Invalid) 505 | return false; 506 | 507 | for (int i = 0; i < mSize; ++i) { 508 | mMap[i] = i; 509 | mLines[i] = -1; 510 | } 511 | 512 | mLineInfos.clear(); 513 | 514 | int lineNo = 0; 515 | int width = 0; 516 | int lineStart = 0; 517 | int lastSpace = -1; 518 | 519 | for (int i = 0; i <= mSize; ++i) { 520 | if (i == mSize) { 521 | mLineInfos.push_back(newLine(lineStart, i, lineNo++)); 522 | break; 523 | } 524 | //if (mCodePoints[i] == ' ') { 525 | if (mTypes[i] == FRIBIDI_TYPE_WS) { 526 | if (lineStart == i) { 527 | lineStart = i + 1; 528 | continue; 529 | } else if (lastSpace == i - 1) { 530 | clearGlyph(i - 1); 531 | lastSpace = i; 532 | continue; 533 | } 534 | lastSpace = i; 535 | } 536 | width += mGeometries[i].xAdvance; 537 | if (mMaxWidth < width) { 538 | if (lineStart == i) { 539 | return false; 540 | } 541 | if (lastSpace > lineStart) { 542 | mLineInfos.push_back(newLine(lineStart, lastSpace, lineNo++)); 543 | width = 0; 544 | lineStart = lastSpace + 1; 545 | i = lastSpace; 546 | continue; 547 | } else { 548 | mLineInfos.push_back(newLine(lineStart, i, lineNo++)); 549 | width = 0; 550 | lineStart = i; 551 | --i; 552 | continue; 553 | } 554 | } 555 | } 556 | 557 | mState = LaidOut; 558 | return true; 559 | } 560 | 561 | LineInfo GlyphString::newLine(int startOffset, int endOffset, int lineNo) 562 | { 563 | LineInfo lineInfo; 564 | lineInfo.startOffset = startOffset; 565 | lineInfo.endOffset = endOffset; 566 | reorderLine(startOffset, endOffset); 567 | 568 | int left = std::numeric_limits::max(); 569 | int right = std::numeric_limits::min(); 570 | int x = 0; 571 | for (int i = startOffset; i < endOffset; ++i) { 572 | int index = reorderedIndex(i); 573 | mLines[index] = lineNo; 574 | left = qMin(left, x + mGeometries[index].left + mGeometries[index].xOffset); 575 | right = qMax(right, x + mGeometries[index].left 576 | + mGeometries[index].xOffset + mImages[index].width()); 577 | x += mGeometries[index].xAdvance; 578 | int top = mGeometries[index].top + mGeometries[index].yOffset; 579 | int bottom = top - mImages[index].height(); 580 | lineInfo.ascent = qMax(lineInfo.ascent, top); 581 | lineInfo.descent = qMin(lineInfo.descent, bottom); 582 | } 583 | 584 | if (right > left) { 585 | lineInfo.width = right - left; 586 | lineInfo.left = left; 587 | } else { 588 | lineInfo.width = 0; 589 | lineInfo.left = 0; 590 | } 591 | 592 | lineInfo.height = lineInfo.ascent - lineInfo.descent; 593 | return lineInfo; 594 | } 595 | 596 | void GlyphString::clearGlyph(int index) 597 | { 598 | mImages[index] = QImage(); 599 | mGeometries[index] = Geometry(); 600 | } 601 | 602 | bool GlyphString::loadGlyphImages(bool useGlyphIndices, bool keepXAdvance) 603 | { 604 | 605 | for (int i = 0; i < mSize; ++i) { 606 | int glyphIndex; 607 | if (useGlyphIndices) 608 | glyphIndex = mGlyphIndices[i]; 609 | else 610 | glyphIndex = FT_Get_Char_Index(mFace, mCodePoints[i]); 611 | 612 | if (FT_Load_Glyph(mFace, glyphIndex, 0)) 613 | continue; 614 | if (FT_Render_Glyph(mFace->glyph, FT_RENDER_MODE_NORMAL)) 615 | continue; 616 | 617 | FT_Bitmap bmp = mFace->glyph->bitmap; 618 | 619 | mGlyphIndices[i] = glyphIndex; 620 | mGeometries[i].top = mFace->glyph->bitmap_top; 621 | mGeometries[i].left = mFace->glyph->bitmap_left; 622 | if (!keepXAdvance) 623 | mGeometries[i].xAdvance = mFace->glyph->advance.x / 64; 624 | 625 | mImages[i] = QImage(bmp.width, bmp.rows, QImage::Format_ARGB32); 626 | 627 | for (int ii = 0; ii < bmp.width; ++ii) 628 | { 629 | for (int jj = 0; jj < bmp.rows; ++jj) 630 | mImages[i].setPixel(ii, jj, qRgba(mFaceColor.red(), 631 | mFaceColor.green(), 632 | mFaceColor.blue(), 633 | bmp.buffer[jj * bmp.pitch + ii])); 634 | } 635 | 636 | } 637 | 638 | return true; 639 | } 640 | 641 | QString GlyphString::scriptShortText(int index) const 642 | { 643 | assert(index >= 0 && index < mSize); 644 | return ::scriptShortText(mScripts[index]); 645 | } 646 | 647 | QString GlyphString::scriptLongText(int index) const 648 | { 649 | assert(index >= 0 && index < mSize); 650 | return ::scriptLongText(mScripts[index]); 651 | } 652 | 653 | QString GlyphString::charTypeShortText(int index) const 654 | { 655 | assert(index >= 0 && index < mSize); 656 | return ::charTypeShortText(mTypes[index]); 657 | } 658 | 659 | QString GlyphString::charTypeLongText(int index) const 660 | { 661 | assert(index >= 0 && index < mSize); 662 | return ::charTypeLongText(mTypes[index]); 663 | } 664 | -------------------------------------------------------------------------------- /glyph_string.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include FT_FREETYPE_H 33 | 34 | #include 35 | 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | struct Geometry 42 | { 43 | int left; 44 | int top; 45 | int xOffset; 46 | int yOffset; 47 | int xAdvance; 48 | int yAdvance; 49 | 50 | Geometry() : left(0), top(0), xOffset(0), yOffset(0), 51 | xAdvance(0), yAdvance(0) 52 | { 53 | } 54 | }; 55 | 56 | struct LineInfo 57 | { 58 | int startOffset; 59 | int endOffset; 60 | int width; 61 | int height; 62 | int ascent; 63 | int descent; 64 | int left; 65 | 66 | LineInfo() : startOffset(0), endOffset(0), width(0), height(0), 67 | ascent(0), descent(0), left(0) 68 | { 69 | } 70 | }; 71 | 72 | struct RunInfo 73 | { 74 | int startOffset; 75 | int endOffset; 76 | hb_buffer_t *buffer; 77 | hb_direction_t direction; 78 | hb_script_t script; 79 | unsigned int glyphCount; 80 | hb_glyph_info_t *glyphInfos; 81 | hb_glyph_position_t *glyphPositions; 82 | 83 | RunInfo() : startOffset(0), endOffset(0), buffer(0), 84 | glyphCount(0), glyphInfos(0), glyphPositions(0) 85 | { 86 | } 87 | }; 88 | 89 | class GlyphString 90 | { 91 | public: 92 | enum State 93 | { 94 | Invalid = 0, 95 | Initialized = 1, 96 | Analyzed = 2, 97 | Shaped = 3, 98 | LaidOut = 4 99 | }; 100 | 101 | GlyphString(); 102 | ~GlyphString(); 103 | bool init(quint32 *codePoints, int size, FT_Face face, 104 | QColor faceColor, FriBidiParType parType, int maxWidth); 105 | bool analyze(bool resolveScripts = true, bool breakOnLevelChange = false); 106 | bool shapeHarfBuzz(); 107 | bool shapeFriBidi(bool removeInvisibleCharacters = true); 108 | bool reorderLine(int startOffset, int endOffset); 109 | bool layout(); 110 | LineInfo newLine(int startOffset, int endOffset, int lineNo); 111 | void clearGlyph(int index); 112 | bool loadGlyphImages(bool useGlyphIndices = false, bool keepXAdvance = false); 113 | 114 | const QVector & lineInfos() const { return mLineInfos; } 115 | const QVector & runInfos() const { return mRunInfos; } 116 | int size() const { return mSize; } 117 | 118 | quint32 codePoint(int index) const 119 | { 120 | assert(index >= 0 && index < mSize); 121 | return mCodePoints[index]; 122 | } 123 | 124 | int glyphIndex(int index) const 125 | { 126 | assert(index >= 0 && index < mSize); 127 | return mGlyphIndices[index]; 128 | } 129 | 130 | const QImage & image(int index) const 131 | { 132 | assert(index >= 0 && index < mSize); 133 | return mImages[index]; 134 | } 135 | 136 | const Geometry & geometry(int index) const 137 | { 138 | assert(index >= 0 && index < mSize); 139 | return mGeometries[index]; 140 | } 141 | 142 | FriBidiLevel level(int index) const 143 | { 144 | if (mState < Analyzed) 145 | return 0; 146 | assert(index >= 0 && index < mSize); 147 | return mLevels[index]; 148 | } 149 | 150 | int run(int index) const 151 | { 152 | if (mState < Analyzed) 153 | return 0; 154 | assert(index >= 0 && index < mSize); 155 | return mRuns[index]; 156 | } 157 | 158 | int line(int index) const 159 | { 160 | if (mState < LaidOut) 161 | return 0; 162 | assert(index >= 0 && index < mSize); 163 | return mLines[index]; 164 | } 165 | 166 | void setMaxWidth(int maxWidth) 167 | { 168 | mMaxWidth = maxWidth; 169 | layout(); 170 | } 171 | 172 | QString scriptShortText(int index) const; 173 | QString scriptLongText(int index) const; 174 | QString charTypeShortText(int index) const; 175 | QString charTypeLongText(int index) const; 176 | 177 | FriBidiStrIndex reorderedIndex(int index) const 178 | { 179 | assert(index >= 0 && index < mSize); 180 | return mMap[index]; 181 | } 182 | 183 | FriBidiParType paragraphType() const 184 | { 185 | return mParType; 186 | } 187 | 188 | int state() const 189 | { 190 | return mState; 191 | } 192 | 193 | protected: 194 | FT_Face mFace; 195 | QColor mFaceColor; 196 | QVector mLineInfos; 197 | QVector mRunInfos; 198 | int mSize; 199 | quint32 *mCodePoints; 200 | int *mGlyphIndices; 201 | QImage *mImages; 202 | Geometry *mGeometries; 203 | int *mRuns; 204 | int *mLines; 205 | int mMaxWidth; 206 | hb_script_t *mScripts; 207 | FriBidiCharType *mTypes; 208 | FriBidiLevel *mLevels; 209 | FriBidiStrIndex *mMap; 210 | FriBidiParType mParType; 211 | int mState; 212 | 213 | private: 214 | GlyphString(const GlyphString &); 215 | GlyphString & operator=(const GlyphString &); 216 | }; 217 | -------------------------------------------------------------------------------- /glyph_string_renderer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "glyph_string_renderer.h" 30 | 31 | GlyphStringRenderer::GlyphStringRenderer(const GlyphString *glyphString, 32 | int penWidth, const QFont &font, 33 | quint32 flags, QColor levelArrowColor, 34 | QColor runArrowColor) 35 | : mGlyphString(glyphString), mPenWidth(penWidth), 36 | mFont(font), mFlags(flags), mLevelArrowColor(levelArrowColor), 37 | mRunArrowColor(runArrowColor) 38 | { 39 | layout(); 40 | } 41 | 42 | void GlyphStringRenderer::reset(const GlyphString *glyphString, 43 | int penWidth, const QFont &font, 44 | quint32 flags, QColor levelArrowColor, 45 | QColor runArrowColor) 46 | { 47 | mGlyphString = glyphString; 48 | mPenWidth = penWidth; 49 | mFont = font; 50 | mFlags = flags; 51 | mLevelArrowColor = levelArrowColor; 52 | mRunArrowColor = runArrowColor; 53 | layout(); 54 | } 55 | 56 | QRectF GlyphStringRenderer::boundingRect() const 57 | { 58 | return mBoundingRect; 59 | } 60 | 61 | void GlyphStringRenderer::paint(QPainter *painter, 62 | const QStyleOptionGraphicsItem *, 63 | QWidget *) 64 | { 65 | for (int i = 0; i < mArrows.size(); ++i) 66 | mArrows[i].paint(painter); 67 | 68 | for (int i = 0; i < mImageIndices.size(); ++i) { 69 | const QImage &image = mGlyphString->image(mImageIndices[i]); 70 | QPoint &p = mImagePositions[i]; 71 | painter->drawImage(p.x(), p.y(), image); 72 | } 73 | } 74 | 75 | void GlyphStringRenderer::layout() 76 | { 77 | int width = 0; 78 | int height = 0; 79 | int lineHeight = 0; 80 | int x = 0; 81 | int y = 0; 82 | 83 | prepareGeometryChange(); 84 | clear(); 85 | 86 | if (mGlyphString->state() < GlyphString::LaidOut) 87 | return; 88 | 89 | for (int i = 0; i < mGlyphString->lineInfos().size(); ++i) { 90 | width = qMax(width, mGlyphString->lineInfos()[i].width); 91 | lineHeight = qMax(lineHeight, mGlyphString->lineInfos()[i].height); 92 | height += mGlyphString->lineInfos()[i].height; 93 | } 94 | width += 10 * mPenWidth; 95 | 96 | QString paragraphText = mGlyphString->paragraphType() == FRIBIDI_PAR_RTL ? 97 | QString("Paragraph base direction: RTL"): 98 | QString("Paragraph base direction: LTR"); 99 | QFontMetrics fontMetrics(mFont); 100 | width = qMax(width, fontMetrics.width(paragraphText + QString("****"))); 101 | 102 | Arrow paragraphArrow; 103 | if (mGlyphString->paragraphType() == FRIBIDI_PAR_RTL) 104 | paragraphArrow.init(mFont, paragraphText, mPenWidth, 105 | mPenWidth / 2, width - mPenWidth / 2, y, true); 106 | else 107 | paragraphArrow.init(mFont, paragraphText, mPenWidth, 108 | mPenWidth / 2, width - mPenWidth / 2, y, false); 109 | 110 | if (mFlags & ParagraphArrow) { 111 | mArrows.push_back(paragraphArrow); 112 | y += paragraphArrow.height(); 113 | } 114 | 115 | for (int i = 0; i < mGlyphString->lineInfos().size(); ++i) { 116 | const LineInfo &lineInfo = mGlyphString->lineInfos()[i]; 117 | int levelArrowY = y; 118 | if (mFlags & LevelArrows) 119 | y += paragraphArrow.height(); 120 | int runArrowY = y; 121 | if (mFlags & RunArrows) 122 | y += paragraphArrow.height(); 123 | 124 | y += lineInfo.ascent; 125 | x = (width - lineInfo.width) / 2; 126 | x -= lineInfo.left; 127 | 128 | int offset = mGlyphString->reorderedIndex(lineInfo.startOffset); 129 | 130 | int lastRun = mGlyphString->run(offset); 131 | int lastRunX = x; 132 | int lastLevel = mGlyphString->level(offset); 133 | int lastLevelX = x; 134 | bool lastRunRTL = mGlyphString->runInfos()[lastRun] 135 | .direction == HB_DIRECTION_RTL; 136 | bool lastLevelRTL = lastLevel & 1; 137 | 138 | for (int j = lineInfo.startOffset; j < lineInfo.endOffset; ++j) { 139 | offset = mGlyphString->reorderedIndex(j); 140 | const Geometry &geometry = mGlyphString->geometry(offset); 141 | QPoint p; 142 | p.setX(x + geometry.left + geometry.xOffset); 143 | p.setY(y - geometry.top - geometry.yOffset); 144 | 145 | if ((mFlags & RunArrows) && (lastRun != mGlyphString->run(offset))) { 146 | mArrows.push_back(Arrow(mFont, QString("%1").arg(lastRun), mPenWidth, 147 | lastRunX, x, runArrowY, lastRunRTL, mRunArrowColor)); 148 | lastRun = mGlyphString->run(offset); 149 | lastRunX = x; 150 | lastRunRTL = mGlyphString->runInfos()[lastRun] 151 | .direction == HB_DIRECTION_RTL; 152 | } 153 | 154 | if ((mFlags & LevelArrows) 155 | && (lastLevel != mGlyphString->level(offset))) 156 | { 157 | mArrows.push_back(Arrow(mFont, QString("%1").arg(lastLevel), mPenWidth, 158 | lastLevelX, x, levelArrowY, lastLevelRTL, mLevelArrowColor)); 159 | lastLevel = mGlyphString->level(offset); 160 | lastLevelX = x; 161 | lastLevelRTL = lastLevel & 1; 162 | } 163 | 164 | assert(p.x() >= 0); 165 | assert(p.x() + mGlyphString->image(offset).width() <= width); 166 | mImagePositions.push_back(p); 167 | mImageIndices.push_back(offset); 168 | x += geometry.xAdvance; 169 | } 170 | 171 | if (mFlags & RunArrows) { 172 | mArrows.push_back(Arrow(mFont, QString("%1").arg(lastRun), mPenWidth, 173 | lastRunX, x, runArrowY, lastRunRTL, mRunArrowColor)); 174 | } 175 | 176 | if (mFlags & LevelArrows) { 177 | mArrows.push_back(Arrow(mFont, QString("%1").arg(lastLevel), mPenWidth, 178 | lastLevelX, x, levelArrowY, lastLevelRTL, mLevelArrowColor)); 179 | } 180 | 181 | y -= lineInfo.ascent; 182 | y += lineHeight; 183 | } 184 | 185 | mBoundingRect = QRectF(0, 0, width, y); 186 | update(); 187 | } 188 | 189 | void GlyphStringRenderer::clear() 190 | { 191 | mArrows.clear(); 192 | mImagePositions.clear(); 193 | mImageIndices.clear(); 194 | } 195 | 196 | void GlyphStringRenderer::setGlyphString(const GlyphString *glyphString) 197 | { 198 | mGlyphString = glyphString; 199 | layout(); 200 | } 201 | 202 | void GlyphStringRenderer::setLevelArrowColor(QColor color) 203 | { 204 | mLevelArrowColor = color; 205 | layout(); 206 | } 207 | 208 | void GlyphStringRenderer::setRunArrowColor(QColor color) 209 | { 210 | mRunArrowColor = color; 211 | layout(); 212 | } 213 | 214 | void GlyphStringRenderer::setRendererFlags(quint32 flags) 215 | { 216 | mFlags = flags; 217 | layout(); 218 | } 219 | 220 | void GlyphStringRenderer::setPenWidth(int width) 221 | { 222 | mPenWidth = width; 223 | layout(); 224 | } 225 | -------------------------------------------------------------------------------- /glyph_string_renderer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | 29 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 30 | #include 31 | #else 32 | #include 33 | #endif 34 | 35 | #include 36 | #include 37 | 38 | #include "glyph_string.h" 39 | #include "arrow.h" 40 | 41 | class GlyphStringRenderer : public QGraphicsItem 42 | { 43 | public: 44 | enum RendererFlags 45 | { 46 | ParagraphArrow = 1 << 0, 47 | LevelArrows = 1 << 1, 48 | RunArrows = 1 << 2 49 | }; 50 | 51 | GlyphStringRenderer(const GlyphString *glyphString, int penWidth, 52 | const QFont &font, quint32 flags, 53 | QColor levelArrowColor = Qt::black, 54 | QColor runArrowColor = Qt::black); 55 | void reset(const GlyphString *glyphString, int penWidth, 56 | const QFont &font, quint32 flags, 57 | QColor levelArrowColor = Qt::black, 58 | QColor runArrowColor = Qt::black); 59 | QRectF boundingRect() const; 60 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 61 | QWidget *widget); 62 | void layout(); 63 | void clear(); 64 | void setGlyphString(const GlyphString *glyphString); 65 | void setLevelArrowColor(QColor color); 66 | void setRunArrowColor(QColor color); 67 | void setRendererFlags(quint32 flags); 68 | void setPenWidth(int width); 69 | 70 | protected: 71 | const GlyphString *mGlyphString; 72 | int mPenWidth; 73 | QFont mFont; 74 | quint32 mFlags; 75 | QRectF mBoundingRect; 76 | QColor mLevelArrowColor; 77 | QColor mRunArrowColor; 78 | 79 | QVector mArrows; 80 | QVector mImagePositions; 81 | QVector mImageIndices; 82 | }; 83 | -------------------------------------------------------------------------------- /glyph_string_visualizer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include "glyph_string_visualizer.h" 26 | 27 | #include 28 | #include 29 | 30 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 31 | #include 32 | #else 33 | #include 34 | #endif 35 | 36 | GlyphStringVisualizer::GlyphStringVisualizer(const GlyphString *glyphString, 37 | int characterSize, 38 | int penWidth, const QFont &font, 39 | quint32 flags) 40 | : mGlyphString(glyphString), mCharacterSize(characterSize), 41 | mPenWidth(penWidth), mFont(font), mFlags(flags) 42 | { 43 | layout(); 44 | } 45 | 46 | void GlyphStringVisualizer::reset(const GlyphString *glyphString, int characterSize, 47 | int penWidth, const QFont &font, quint32 flags) 48 | { 49 | mGlyphString = glyphString; 50 | mCharacterSize = characterSize; 51 | mPenWidth = penWidth; 52 | mFont = font; 53 | mFlags = flags; 54 | 55 | layout(); 56 | } 57 | 58 | void GlyphStringVisualizer::setGlyphString(const GlyphString *glyphString) 59 | { 60 | mGlyphString = glyphString; 61 | layout(); 62 | } 63 | 64 | void GlyphStringVisualizer::setPenWidth(int penWidth) 65 | { 66 | mPenWidth = penWidth; 67 | layout(); 68 | } 69 | 70 | void GlyphStringVisualizer::setVisualizerFlags(quint32 flags) 71 | { 72 | mFlags = flags; 73 | layout(); 74 | } 75 | 76 | void GlyphStringVisualizer::clear() 77 | { 78 | mCells.clear(); 79 | mArrows.clear(); 80 | mImagePositions.clear(); 81 | 82 | QList children = childItems(); 83 | for (int i = children.size() - 1; i >= 0; --i) 84 | delete children[i]; 85 | } 86 | 87 | QRectF GlyphStringVisualizer::boundingRect() const 88 | { 89 | return mBoundingRect; 90 | } 91 | 92 | void GlyphStringVisualizer::paint(QPainter *painter, 93 | const QStyleOptionGraphicsItem *, 94 | QWidget *) 95 | { 96 | 97 | QPen pen; 98 | pen.setWidth(mPenWidth); 99 | pen.setJoinStyle(Qt::RoundJoin); 100 | pen.setCapStyle(Qt::RoundCap); 101 | painter->setPen(pen); 102 | 103 | for (int i = 0; i < mCells.size(); ++i) 104 | painter->drawRect(mCells[i]); 105 | 106 | for (int i = 0; i < mArrows.size(); ++i) 107 | mArrows[i].paint(painter); 108 | 109 | for (int i = 0; i < mImagePositions.size(); ++i) 110 | painter->drawImage(mImagePositions[i].x(), mImagePositions[i].y(), 111 | mGlyphString->image(i)); 112 | 113 | } 114 | 115 | void GlyphStringVisualizer::layout() 116 | { 117 | prepareGeometryChange(); 118 | clear(); 119 | 120 | if (mGlyphString->state() == GlyphString::Invalid) 121 | return; 122 | 123 | QFontMetrics fontMetrics(mFont); 124 | int fontHeight = fontMetrics.height(); 125 | int cellWidth = mCharacterSize + mPenWidth; 126 | int cellHeight = mCharacterSize + 2 * mPenWidth; 127 | int margin = fontMetrics.width("Make the margin big enough"); 128 | int x = 0; 129 | int y = 0; 130 | Arrow tempArrow(mFont, "text", mPenWidth, 0, 1000, 0, false); 131 | 132 | int lineArrowY = y; 133 | int arrowHeight = qMax(tempArrow.height(), fontHeight); 134 | if (mFlags & LineArrows) y += arrowHeight; 135 | 136 | int levelArrowY = y; 137 | if (mFlags & LevelArrows) y += arrowHeight; 138 | 139 | int runArrowY = y; 140 | if (mFlags & RunArrows) y += arrowHeight; 141 | 142 | int cellsY = y; 143 | y += cellHeight; 144 | 145 | int codePointsY = y; 146 | if (mFlags & CodePoints) y += fontHeight; 147 | 148 | int glyphIndicesY = y; 149 | if (mFlags & GlyphIndices) y += fontHeight; 150 | 151 | int charTypesY = y; 152 | if (mFlags & CharTypes) y += fontHeight; 153 | 154 | int scriptsY = y; 155 | if (mFlags & Scripts) y += fontHeight; 156 | 157 | int leftsY = 0, topsY = 0, xOffsetsY = 0, 158 | yOffsetsY = 0, xAdvancesY = 0, yAdvancesY = 0; 159 | 160 | if (mFlags & Geometries) { 161 | leftsY = y; 162 | y += fontHeight; 163 | 164 | topsY = y; 165 | y += fontHeight; 166 | 167 | xOffsetsY = y; 168 | y += fontHeight; 169 | 170 | yOffsetsY = y; 171 | y += fontHeight; 172 | 173 | xAdvancesY = y; 174 | y += fontHeight; 175 | 176 | yAdvancesY = y; 177 | y += fontHeight; 178 | } 179 | 180 | int indicesY = y; 181 | if (mFlags & Indices) y += fontHeight; 182 | 183 | int reorderedIndicesY = y; 184 | if (mFlags & ReorderedIndices) y += fontHeight; 185 | 186 | // Lay out the margin 187 | if (mFlags & LineArrows) 188 | addTextItem(fontMetrics, 0, lineArrowY, margin, arrowHeight, "Lines:"); 189 | if (mFlags & LevelArrows) 190 | addTextItem(fontMetrics, 0, levelArrowY, margin, arrowHeight, "Levels:"); 191 | if (mFlags & RunArrows) 192 | addTextItem(fontMetrics, 0, runArrowY, margin, arrowHeight, "Runs:"); 193 | addTextItem(fontMetrics, 0, cellsY, margin, cellHeight, 194 | "Glyphs:"); 195 | if (mFlags & CodePoints) 196 | addTextItem(fontMetrics, 0, codePointsY, margin, fontHeight, "Code Point:"); 197 | if (mFlags & GlyphIndices) 198 | addTextItem(fontMetrics, 0, glyphIndicesY, margin, fontHeight, "Glyph Index:"); 199 | if (mFlags & CharTypes) 200 | addTextItem(fontMetrics, 0, charTypesY, margin, fontHeight, "Bidi Char Type:"); 201 | if (mFlags & Scripts) 202 | addTextItem(fontMetrics, 0, scriptsY, margin, fontHeight, "Script:"); 203 | if (mFlags & Geometries) { 204 | addTextItem(fontMetrics, 0, leftsY, margin, fontHeight, "Left:"); 205 | addTextItem(fontMetrics, 0, topsY, margin, fontHeight, "Top:"); 206 | addTextItem(fontMetrics, 0, xOffsetsY, margin, fontHeight, "X Offset:"); 207 | addTextItem(fontMetrics, 0, yOffsetsY, margin, fontHeight, "Y Offset:"); 208 | addTextItem(fontMetrics, 0, xAdvancesY, margin, fontHeight, "X Advance:"); 209 | addTextItem(fontMetrics, 0, yAdvancesY, margin, fontHeight, "Y Advance:"); 210 | } 211 | if (mFlags & Indices) 212 | addTextItem(fontMetrics, 0, indicesY, margin, fontHeight, "Index:"); 213 | if (mFlags & ReorderedIndices) 214 | addTextItem(fontMetrics, 0, reorderedIndicesY, margin, fontHeight, "Reordered Index:"); 215 | 216 | int height = y; 217 | x = margin + mPenWidth / 2; 218 | y = cellsY + mPenWidth / 2; 219 | 220 | int lastRun = mGlyphString->run(0); 221 | int lastRunStart = 0; 222 | int lastLevel = mGlyphString->level(0); 223 | int lastLevelStart = 0; 224 | int lastLine = mGlyphString->line(0); 225 | int lastLineStart = 0; 226 | 227 | for (int i = 0; i <= mGlyphString->size(); ++i) { 228 | int currentX = x + i * cellWidth; 229 | if ((mFlags & RunArrows) && (i == mGlyphString->size() 230 | || mGlyphString->run(i) != lastRun)) 231 | { 232 | QString arrowText = QString("%1").arg(lastRun); 233 | Arrow arrow(mFont, arrowText, mPenWidth, 234 | x + lastRunStart * cellWidth, 235 | x + i * cellWidth, runArrowY, false); 236 | mArrows.push_back(arrow); 237 | if ( i < mGlyphString->size() ) { 238 | lastRun = mGlyphString->run(i); 239 | lastRunStart = i; 240 | } 241 | } 242 | 243 | if ((mFlags & LevelArrows) && (i == mGlyphString->size() 244 | || mGlyphString->level(i) != lastLevel)) 245 | { 246 | QString arrowText = QString("%1").arg(lastLevel); 247 | Arrow arrow(mFont, arrowText, mPenWidth, 248 | x + lastLevelStart * cellWidth, 249 | x + i * cellWidth, levelArrowY, false); 250 | mArrows.push_back(arrow); 251 | if ( i < mGlyphString->size() ) { 252 | lastLevel = mGlyphString->level(i); 253 | lastLevelStart = i; 254 | } 255 | } 256 | 257 | if ((mFlags & LineArrows) && (i == mGlyphString->size() 258 | || mGlyphString->line(i) != lastLine)) 259 | { 260 | if ( lastLine != -1 ) { 261 | QString arrowText = QString("%1").arg(lastLine); 262 | Arrow arrow(mFont, arrowText, mPenWidth, 263 | x + lastLineStart * cellWidth, 264 | x + i * cellWidth, lineArrowY, false); 265 | mArrows.push_back(arrow); 266 | } 267 | if ( i < mGlyphString->size() ) { 268 | lastLine = mGlyphString->line(i); 269 | lastLineStart = i; 270 | } 271 | } 272 | 273 | if ( i == mGlyphString->size() ) 274 | break; 275 | 276 | QRect cell(currentX, y, cellWidth, cellWidth); 277 | mCells.push_back(cell); 278 | 279 | int imageX = currentX 280 | + (cellWidth - mGlyphString->image(i).width()) / 2; 281 | int imageY = y 282 | + (cellWidth - mGlyphString->image(i).height()) / 2; 283 | mImagePositions.push_back(QPoint(imageX, imageY)); 284 | 285 | QString text; 286 | QString toolTip; 287 | 288 | if (mFlags & CodePoints) { 289 | text = text.sprintf("0x%04x", mGlyphString->codePoint(i)); 290 | addTextItem(fontMetrics, currentX, codePointsY, 291 | cellWidth, fontHeight, text); 292 | } 293 | if (mFlags & GlyphIndices) { 294 | text = QString("%1").arg(mGlyphString->glyphIndex(i)); 295 | addTextItem(fontMetrics, currentX, glyphIndicesY, 296 | cellWidth, fontHeight, text); 297 | } 298 | if (mFlags & CharTypes) { 299 | text = mGlyphString->charTypeShortText(i); 300 | toolTip = mGlyphString->charTypeLongText(i); 301 | addTextItem(fontMetrics, currentX, charTypesY, 302 | cellWidth, fontHeight, text, toolTip); 303 | } 304 | if (mFlags & Scripts) { 305 | text = mGlyphString->scriptShortText(i); 306 | toolTip = mGlyphString->scriptLongText(i); 307 | addTextItem(fontMetrics, currentX, scriptsY, 308 | cellWidth, fontHeight, text, toolTip); 309 | } 310 | if (mFlags & Geometries) { 311 | text = QString("%1").arg(mGlyphString->geometry(i).left); 312 | addTextItem(fontMetrics, currentX, leftsY, 313 | cellWidth, fontHeight, text); 314 | text = QString("%1").arg(mGlyphString->geometry(i).top); 315 | addTextItem(fontMetrics, currentX, topsY, 316 | cellWidth, fontHeight, text); 317 | text = QString("%1").arg(mGlyphString->geometry(i).xOffset); 318 | addTextItem(fontMetrics, currentX, xOffsetsY, 319 | cellWidth, fontHeight, text); 320 | text = QString("%1").arg(mGlyphString->geometry(i).yOffset); 321 | addTextItem(fontMetrics, currentX, yOffsetsY, 322 | cellWidth, fontHeight, text); 323 | text = QString("%1").arg(mGlyphString->geometry(i).xAdvance); 324 | addTextItem(fontMetrics, currentX, xAdvancesY, 325 | cellWidth, fontHeight, text); 326 | text = QString("%1").arg(mGlyphString->geometry(i).yAdvance); 327 | addTextItem(fontMetrics, currentX, yAdvancesY, 328 | cellWidth, fontHeight, text); 329 | } 330 | if (mFlags & Indices) { 331 | text = QString("%1").arg(i); 332 | addTextItem(fontMetrics, currentX, indicesY, 333 | cellWidth, fontHeight, text); 334 | } 335 | if (mFlags & ReorderedIndices) { 336 | text = QString("%1").arg(mGlyphString->reorderedIndex(i)); 337 | addTextItem(fontMetrics, currentX, reorderedIndicesY, 338 | cellWidth, fontHeight, text); 339 | } 340 | } 341 | 342 | mBoundingRect = QRectF(0, 0, margin + mGlyphString->size() * cellWidth 343 | + mPenWidth, 344 | height); 345 | update(); 346 | 347 | } 348 | 349 | void GlyphStringVisualizer::addTextItem(const QFontMetrics &fontMetrics, 350 | int x, int y, 351 | int totalWidth, int totalHeight, 352 | const QString &text, 353 | const QString &toolTip) 354 | { 355 | QGraphicsSimpleTextItem *item; 356 | int textWidth = fontMetrics.width(text); 357 | 358 | item = new QGraphicsSimpleTextItem(this); 359 | item->setFont(mFont); 360 | item->setText(text); 361 | if (!toolTip.isEmpty()) 362 | item->setToolTip(toolTip); 363 | item->setPos(x + (totalWidth - textWidth) / 2, 364 | y + (totalHeight - fontMetrics.height()) / 2); 365 | } 366 | -------------------------------------------------------------------------------- /glyph_string_visualizer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | 29 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 30 | #include 31 | #else 32 | #include 33 | #endif 34 | 35 | #include 36 | #include 37 | 38 | #include "glyph_string.h" 39 | #include "arrow.h" 40 | 41 | class GlyphStringVisualizer : public QGraphicsItem 42 | { 43 | public: 44 | enum VisualizerFlags 45 | { 46 | LineArrows = 1 << 0, 47 | LevelArrows = 1 << 1, 48 | RunArrows = 1 << 2, 49 | CodePoints = 1 << 3, 50 | GlyphIndices = 1 << 4, 51 | CharTypes = 1 << 5, 52 | Scripts = 1 << 6, 53 | Geometries = 1 << 7, 54 | Indices = 1 << 8, 55 | ReorderedIndices = 1 << 9 56 | }; 57 | 58 | GlyphStringVisualizer(const GlyphString *glyphString, int characterSize, 59 | int penWidth, const QFont &font, quint32 flags); 60 | void reset(const GlyphString *glyphString, int characterSize, 61 | int penWidth, const QFont &font, quint32 flags); 62 | void clear(); 63 | QRectF boundingRect() const; 64 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 65 | QWidget *widget); 66 | void layout(); 67 | void addTextItem(const QFontMetrics &fontMetrics, int x, int y, 68 | int totalWidth, int totalHeight, const QString &text, 69 | const QString &toolTip = QString()); 70 | void setGlyphString(const GlyphString *glyphString); 71 | void setPenWidth(int penWidth); 72 | void setVisualizerFlags(quint32 flags); 73 | 74 | protected: 75 | const GlyphString *mGlyphString; 76 | int mCharacterSize; 77 | int mPenWidth; 78 | QFont mFont; 79 | quint32 mFlags; 80 | QRectF mBoundingRect; 81 | 82 | QVector mArrows; 83 | QVector mImagePositions; 84 | QVector mCells; 85 | }; 86 | -------------------------------------------------------------------------------- /graphicsview.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include 26 | 27 | #include "graphicsview.h" 28 | 29 | GraphicsView::GraphicsView(QWidget *parent) : 30 | QGraphicsView(parent), mScale(1.0) 31 | { 32 | setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); 33 | setDragMode(ScrollHandDrag); 34 | } 35 | 36 | void GraphicsView::wheelEvent(QWheelEvent *event) 37 | { 38 | 39 | setTransformationAnchor(QGraphicsView::AnchorUnderMouse); 40 | 41 | if (event->delta() > 0) 42 | mScale *= 1.15; 43 | else 44 | mScale /= 1.15; 45 | 46 | mScale = qMax(0.01, qMin(mScale, 3.0)); 47 | setScale(mScale); 48 | } 49 | 50 | void GraphicsView::setScale(int scale) 51 | { 52 | if (mScale * 1000 == scale) 53 | return; 54 | setScale(static_cast(scale) / 1000); 55 | } 56 | 57 | void GraphicsView::setScale(qreal scale) 58 | { 59 | mScale = scale; 60 | QTransform transform; 61 | setTransform(transform.scale(mScale, mScale)); 62 | emit scaleChanged(scale * 1000); 63 | } 64 | -------------------------------------------------------------------------------- /graphicsview.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | 29 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 30 | #include 31 | #else 32 | #include 33 | #endif 34 | 35 | class QGraphicsScene; 36 | 37 | class GraphicsView : public QGraphicsView 38 | { 39 | Q_OBJECT 40 | 41 | public: 42 | GraphicsView(QWidget *parent = 0); 43 | virtual void wheelEvent(QWheelEvent* event); 44 | public slots: 45 | void setScale(int scale); 46 | signals: 47 | void scaleChanged(int); 48 | protected: 49 | qreal mScale; 50 | void setScale(qreal scale); 51 | }; 52 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include 26 | 27 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 28 | #include 29 | #else 30 | #include 31 | #endif 32 | 33 | #include "mainwindow.h" 34 | 35 | int main(int argc, char **argv) 36 | { 37 | QApplication app(argc, argv); 38 | MainWindow mw; 39 | 40 | mw.show(); 41 | return app.exec(); 42 | } 43 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include "mainwindow.h" 26 | #include "ui_mainwindow.h" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 34 | #include 35 | #include 36 | #else 37 | #include 38 | #include 39 | #endif 40 | 41 | MainWindow::MainWindow(QWidget *parent) : 42 | QMainWindow(parent), mGraphicsScene(0), mFontPath("fonts/"), 43 | mFreeTypeInitialized(false), mRendering(false), 44 | mLibrary(0), mFace(0), mResourceFace(0), mRawGlyphString(0), 45 | mShapedGlyphString(0), mRawVisualizer(0), mShapedVisualizer(0), 46 | mShapedRenderer(0), ui(new Ui::MainWindow) 47 | { 48 | ui->setupUi(this); 49 | setWindowTitle("BidiRenderer"); 50 | statusBar(); 51 | 52 | loadSamples(); 53 | ui->fontPathLE->setText(mFontPath); 54 | listFonts(); 55 | 56 | ui->levelsColorPickerLabel->setColor(Qt::blue); 57 | mFontSize = ui->fontSizeSB->value(); 58 | mPenWidth = ui->penWidthSB->value(); 59 | ui->lineWidthSlider->setRange(mFontSize * 4, mFontSize * 40); 60 | ui->lineWidthSlider->setValue(mFontSize * 40); 61 | 62 | if (FT_Init_FreeType(&mLibrary)) { 63 | statusBar()->showMessage("Error initializing FreeType", messageTimeout); 64 | return; 65 | } 66 | mFreeTypeInitialized = true; 67 | 68 | QFile fontFile(":/files/fonts/DejaVuSans.ttf"); 69 | if (fontFile.open(QIODevice::ReadOnly)) 70 | mResourceFaceData = fontFile.readAll(); 71 | 72 | if (FT_New_Memory_Face(mLibrary, 73 | reinterpret_cast(mResourceFaceData.constData()), 74 | fontFile.size(), 0, &mResourceFace)) 75 | { 76 | statusBar()->showMessage("Error loading DejaVuSans.ttf", messageTimeout); 77 | } 78 | else 79 | { 80 | if (FT_Set_Pixel_Sizes(mResourceFace, 0, mFontSize)) { 81 | statusBar()->showMessage("Error setting font pixel size", messageTimeout); 82 | FT_Done_Face(mResourceFace); 83 | mResourceFace = 0; 84 | } else { 85 | mFace = mResourceFace; 86 | } 87 | } 88 | 89 | 90 | mGraphicsScene = new QGraphicsScene(this); 91 | ui->graphicsView->setScene(mGraphicsScene); 92 | 93 | connect(ui->actionE_xit, SIGNAL(triggered()), this, SLOT(close())); 94 | connect(ui->action_Panel, SIGNAL(toggled(bool)), 95 | ui->dockWidget, SLOT(setVisible(bool))); 96 | connect(ui->dockWidget, SIGNAL(visibilityChanged(bool)), 97 | ui->action_Panel, SLOT(setChecked(bool))); 98 | connect(ui->textCombo->lineEdit(), SIGNAL(returnPressed()), 99 | ui->renderButton, SLOT(animateClick())); 100 | connect(ui->renderButton, SIGNAL(clicked()), this, SLOT(render())); 101 | connect(ui->rtlRB, SIGNAL(clicked()), this, SLOT(render())); 102 | connect(ui->ltrRB, SIGNAL(clicked()), this, SLOT(render())); 103 | connect(ui->autoRB, SIGNAL(clicked()), this, SLOT(render())); 104 | connect(ui->resolveScriptsCB, SIGNAL(clicked()), this, SLOT(render())); 105 | connect(ui->breakRunsCB, SIGNAL(clicked()), this, SLOT(render())); 106 | 107 | connect(ui->linesCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 108 | connect(ui->levelsCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 109 | connect(ui->runsCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 110 | connect(ui->codePointsCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 111 | connect(ui->glyphIndicesCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 112 | connect(ui->charTypesCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 113 | connect(ui->scriptsCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 114 | connect(ui->geometriesCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 115 | connect(ui->indicesCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 116 | connect(ui->reorderedIndicesCB, SIGNAL(clicked()), this, SLOT(setVisualizerFlags())); 117 | 118 | connect(ui->paragraphCB, SIGNAL(clicked()), this, SLOT(setRendererFlags())); 119 | connect(ui->levelsGB, SIGNAL(clicked()), this, SLOT(setRendererFlags())); 120 | connect(ui->runsGB, SIGNAL(clicked()), this, SLOT(setRendererFlags())); 121 | connect(ui->levelsColorPickerLabel, SIGNAL(colorChanged(QColor)), 122 | this, SLOT(setRendererLevelColor(QColor))); 123 | connect(ui->runsColorPickerLabel, SIGNAL(colorChanged(QColor)), 124 | this, SLOT(setRendererRunColor(QColor))); 125 | 126 | connect(ui->shapeHarfBuzzRB, SIGNAL(clicked()), this, SLOT(render())); 127 | connect(ui->shapeFriBidiRB, SIGNAL(clicked()), this, SLOT(render())); 128 | connect(ui->removeZeroWidthCB, SIGNAL(clicked()), this, SLOT(render())); 129 | connect(ui->shapeFriBidiRB, SIGNAL(toggled(bool)), 130 | ui->removeZeroWidthCB, SLOT(setEnabled(bool))); 131 | 132 | connect(ui->fontPathButton, SIGNAL(clicked()), this, SLOT(setFontPath())); 133 | connect(ui->fontsCombo, SIGNAL(currentIndexChanged(QString)), 134 | this, SLOT(setFontFile(QString))); 135 | connect(ui->zoomSlider, SIGNAL(valueChanged(int)), 136 | ui->graphicsView, SLOT(setScale(int))); 137 | connect(ui->graphicsView, SIGNAL(scaleChanged(int)), 138 | ui->zoomSlider, SLOT(setValue(int))); 139 | connect(ui->lineWidthSlider, SIGNAL(valueChanged(int)), 140 | this, SLOT(setMaxLineWidth(int))); 141 | connect(ui->fontSizeSB, SIGNAL(valueChanged(int)), 142 | this, SLOT(setFontSize(int))); 143 | connect(ui->fontColorPickerLabel, SIGNAL(colorChanged(QColor)), 144 | this, SLOT(render())); 145 | connect(ui->penWidthSB, SIGNAL(valueChanged(int)), 146 | this, SLOT(setPenWidth(int))); 147 | } 148 | 149 | MainWindow::~MainWindow() 150 | { 151 | delete ui; 152 | delete mGraphicsScene; 153 | delete mRawGlyphString; 154 | delete mShapedGlyphString; 155 | 156 | if (mFace && (mFace != mResourceFace)) { 157 | FT_Done_Face(mFace); 158 | FT_Done_Face(mResourceFace); 159 | } else { 160 | FT_Done_Face(mResourceFace); 161 | } 162 | 163 | if (mLibrary) 164 | FT_Done_FreeType(mLibrary); 165 | } 166 | 167 | void MainWindow::render() 168 | { 169 | if (!mFreeTypeInitialized) { 170 | statusBar()->showMessage("FreeType has not been initialized", messageTimeout); 171 | return; 172 | } 173 | if (!mFace) { 174 | statusBar()->showMessage("No font selected", messageTimeout); 175 | return; 176 | } 177 | if (ui->textCombo->lineEdit()->text().isEmpty()) 178 | return; 179 | 180 | QTextCodec *codec = QTextCodec::codecForName("UTF-32"); 181 | QByteArray byteArray = codec->fromUnicode(ui->textCombo->lineEdit()->text()); 182 | 183 | QFont font("Arial"); 184 | font.setPixelSize(mFontSize / 4); 185 | 186 | delete mRawGlyphString; 187 | mRawGlyphString = 0; 188 | delete mShapedGlyphString; 189 | mShapedGlyphString = 0; 190 | 191 | FriBidiParType parType; 192 | if (ui->rtlRB->isChecked()) 193 | parType = FRIBIDI_PAR_RTL; 194 | else if (ui->ltrRB->isChecked()) 195 | parType = FRIBIDI_PAR_LTR; 196 | else 197 | parType = FRIBIDI_PAR_ON; 198 | 199 | mRawGlyphString = new GlyphString(); 200 | mRawGlyphString->init(((quint32*) byteArray.constData()) + 1, 201 | ui->textCombo->lineEdit()->text().size(), 202 | mFace, ui->fontColorPickerLabel->color(), 203 | parType, ui->lineWidthSlider->value()); 204 | mShapedGlyphString = new GlyphString(); 205 | mShapedGlyphString->init(((quint32*) byteArray.constData()) + 1, 206 | ui->textCombo->lineEdit()->text().size(), 207 | mFace, ui->fontColorPickerLabel->color(), 208 | parType, ui->lineWidthSlider->value()); 209 | mRawGlyphString->analyze(ui->resolveScriptsCB->isChecked(), 210 | ui->breakRunsCB->isChecked()); 211 | mShapedGlyphString->analyze(ui->resolveScriptsCB->isChecked(), 212 | ui->breakRunsCB->isChecked()); 213 | 214 | if (ui->shapeHarfBuzzRB->isChecked()) 215 | mShapedGlyphString->shapeHarfBuzz(); 216 | else 217 | mShapedGlyphString->shapeFriBidi(ui->removeZeroWidthCB->isChecked()); 218 | mShapedGlyphString->layout(); 219 | 220 | quint32 vFlags = visualizerFlags(); 221 | quint32 rFlags = rendererFlags(); 222 | 223 | if (mRendering) { 224 | mRawVisualizer->reset(mRawGlyphString, mFontSize, mPenWidth, 225 | font, vFlags); 226 | mShapedVisualizer->reset(mShapedGlyphString, mFontSize, mPenWidth, 227 | font, vFlags); 228 | mShapedRenderer->reset(mShapedGlyphString, mPenWidth, 229 | font, rFlags, ui->levelsColorPickerLabel->color(), 230 | ui->runsColorPickerLabel->color()); 231 | 232 | } else { 233 | mRawVisualizer = new GlyphStringVisualizer(mRawGlyphString, mFontSize, 234 | mPenWidth, font, vFlags); 235 | mShapedVisualizer = new GlyphStringVisualizer(mShapedGlyphString, mFontSize, 236 | mPenWidth, font, vFlags); 237 | mShapedRenderer = new GlyphStringRenderer(mShapedGlyphString, mPenWidth, 238 | font, rFlags, ui->levelsColorPickerLabel->color(), 239 | ui->runsColorPickerLabel->color()); 240 | 241 | mRawVisualizer->setFlags(QGraphicsItem::ItemIsSelectable | 242 | QGraphicsItem::ItemIsMovable); 243 | mShapedVisualizer->setFlags(QGraphicsItem::ItemIsSelectable | 244 | QGraphicsItem::ItemIsMovable); 245 | mShapedRenderer->setFlags(QGraphicsItem::ItemIsSelectable | 246 | QGraphicsItem::ItemIsMovable); 247 | 248 | mShapedVisualizer->setPos(0, 2 * mRawVisualizer->boundingRect().height()); 249 | mShapedRenderer->setPos(0, 2 * mRawVisualizer->boundingRect().height() 250 | + 2 * mShapedVisualizer->boundingRect().height()); 251 | 252 | mGraphicsScene->addItem(mRawVisualizer); 253 | mGraphicsScene->addItem(mShapedVisualizer); 254 | mGraphicsScene->addItem(mShapedRenderer); 255 | 256 | mRendering = true; 257 | } 258 | 259 | update(); 260 | } 261 | 262 | void MainWindow::setVisualizerFlags() 263 | { 264 | if (!mRendering) 265 | return; 266 | mRawVisualizer->setVisualizerFlags(visualizerFlags()); 267 | mShapedVisualizer->setVisualizerFlags(visualizerFlags()); 268 | } 269 | 270 | void MainWindow::setRendererFlags() 271 | { 272 | if (!mRendering) 273 | return; 274 | mShapedRenderer->setRendererFlags(rendererFlags()); 275 | } 276 | 277 | void MainWindow::setRendererLevelColor(QColor color) 278 | { 279 | if (!mRendering) 280 | return; 281 | mShapedRenderer->setLevelArrowColor(color); 282 | } 283 | 284 | void MainWindow::setRendererRunColor(QColor color) 285 | { 286 | if (!mRendering) 287 | return; 288 | mShapedRenderer->setRunArrowColor(color); 289 | } 290 | 291 | void MainWindow::setFontPath() 292 | { 293 | QString path = QFileDialog::getExistingDirectory(this, 294 | "Set fonts directory", mFontPath, 295 | QFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog); 296 | if (path.isEmpty()) 297 | return; 298 | mFontPath = path; 299 | ui->fontPathLE->setText(path); 300 | listFonts(); 301 | } 302 | 303 | void MainWindow::setFontFile(const QString &fontFile) 304 | { 305 | if (fontFile.isEmpty()) 306 | return; 307 | 308 | FT_Face newFace; 309 | if (FT_New_Face(mLibrary, fontFile.toStdString().c_str(), 0, &newFace)) { 310 | statusBar()->showMessage(QString("Error loading %1") 311 | .arg(fontFile), messageTimeout); 312 | return; 313 | } 314 | if (FT_Set_Pixel_Sizes(newFace, 0, mFontSize)) { 315 | statusBar()->showMessage(QString("Error setting font size"), 316 | messageTimeout); 317 | FT_Done_Face(newFace); 318 | return; 319 | } 320 | if (mFace != mResourceFace) 321 | FT_Done_Face(mFace); 322 | mFace = newFace; 323 | render(); 324 | } 325 | 326 | void MainWindow::setMaxLineWidth(int width) 327 | { 328 | mShapedGlyphString->setMaxWidth(width); 329 | mShapedVisualizer->layout(); 330 | mShapedRenderer->layout(); 331 | } 332 | 333 | void MainWindow::setFontSize(int size) 334 | { 335 | if (FT_Set_Pixel_Sizes(mFace, 0, size)) { 336 | statusBar()->showMessage("Error setting font size"); 337 | return; 338 | } 339 | mFontSize = size; 340 | ui->lineWidthSlider->setRange(size * 4, size * 40); 341 | ui->penWidthSB->setMaximum(size / 10); 342 | render(); 343 | } 344 | 345 | void MainWindow::setPenWidth(int width) 346 | { 347 | mPenWidth = width; 348 | mRawVisualizer->setPenWidth(width); 349 | mShapedVisualizer->setPenWidth(width); 350 | mShapedRenderer->setPenWidth(width); 351 | } 352 | 353 | bool MainWindow::loadSamples() 354 | { 355 | QFile file(":/files/samples.txt"); 356 | if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) 357 | return false; 358 | 359 | QTextStream textStream(&file); 360 | textStream.setCodec("UTF-8"); 361 | 362 | while(!textStream.atEnd()) { 363 | QString line = textStream.readLine(); 364 | ui->textCombo->insertItem(ui->textCombo->count(), line); 365 | } 366 | return true; 367 | } 368 | 369 | bool MainWindow::listFonts() 370 | { 371 | ui->fontsCombo->clear(); 372 | QStringList nameFilters; 373 | nameFilters << "*.ttf"; 374 | QDirIterator it(mFontPath, nameFilters, QDir::NoFilter, QDirIterator::Subdirectories); 375 | while (it.hasNext()) { 376 | ui->fontsCombo->insertItem(ui->fontsCombo->count(), it.next()); 377 | } 378 | return true; 379 | } 380 | 381 | quint32 MainWindow::visualizerFlags() 382 | { 383 | quint32 flags = 0; 384 | 385 | if (ui->linesCB->isChecked()) 386 | flags |= GlyphStringVisualizer::LineArrows; 387 | if (ui->levelsCB->isChecked()) 388 | flags |= GlyphStringVisualizer::LevelArrows; 389 | if (ui->runsCB->isChecked()) 390 | flags |= GlyphStringVisualizer::RunArrows; 391 | if (ui->codePointsCB->isChecked()) 392 | flags |= GlyphStringVisualizer::CodePoints; 393 | if (ui->glyphIndicesCB->isChecked()) 394 | flags |= GlyphStringVisualizer::GlyphIndices; 395 | if (ui->charTypesCB->isChecked()) 396 | flags |= GlyphStringVisualizer::CharTypes; 397 | if (ui->scriptsCB->isChecked()) 398 | flags |= GlyphStringVisualizer::Scripts; 399 | if (ui->geometriesCB->isChecked()) 400 | flags |= GlyphStringVisualizer::Geometries; 401 | if (ui->indicesCB->isChecked()) 402 | flags |= GlyphStringVisualizer::Indices; 403 | if (ui->reorderedIndicesCB->isChecked()) 404 | flags |= GlyphStringVisualizer::ReorderedIndices; 405 | 406 | return flags; 407 | } 408 | 409 | quint32 MainWindow::rendererFlags() 410 | { 411 | quint32 flags = 0; 412 | if (ui->paragraphCB->isChecked()) 413 | flags |= GlyphStringRenderer::ParagraphArrow; 414 | if (ui->levelsGB->isChecked()) 415 | flags |= GlyphStringRenderer::LevelArrows; 416 | if (ui->runsGB->isChecked()) 417 | flags |= GlyphStringRenderer::RunArrows; 418 | return flags; 419 | } 420 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Salah-Eddin Shaban 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #ifndef MAINWINDOW_H 26 | #define MAINWINDOW_H 27 | 28 | #include 29 | 30 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 31 | #include 32 | #include 33 | #else 34 | #include 35 | #include 36 | #endif 37 | 38 | #include "glyph_string.h" 39 | #include "glyph_string_visualizer.h" 40 | #include "glyph_string_renderer.h" 41 | 42 | namespace Ui { 43 | class MainWindow; 44 | } 45 | 46 | class MainWindow : public QMainWindow 47 | { 48 | Q_OBJECT 49 | 50 | public: 51 | explicit MainWindow(QWidget *parent = 0); 52 | ~MainWindow(); 53 | 54 | public slots: 55 | void render(); 56 | void setVisualizerFlags(); 57 | void setRendererFlags(); 58 | void setRendererLevelColor(QColor color); 59 | void setRendererRunColor(QColor color); 60 | void setFontPath(); 61 | void setFontFile(const QString &fontFile); 62 | void setMaxLineWidth(int width); 63 | void setFontSize(int size); 64 | void setPenWidth(int width); 65 | 66 | protected: 67 | static const int messageTimeout = 5000; 68 | bool loadSamples(); 69 | bool listFonts(); 70 | quint32 visualizerFlags(); 71 | quint32 rendererFlags(); 72 | 73 | QGraphicsScene *mGraphicsScene; 74 | QString mFontPath; 75 | bool mFreeTypeInitialized; 76 | bool mRendering; 77 | FT_Library mLibrary; 78 | FT_Face mFace; 79 | FT_Face mResourceFace; 80 | QByteArray mResourceFaceData; 81 | int mFontSize; 82 | int mPenWidth; 83 | GlyphString *mRawGlyphString; 84 | GlyphString *mShapedGlyphString; 85 | GlyphStringVisualizer *mRawVisualizer; 86 | GlyphStringVisualizer *mShapedVisualizer; 87 | GlyphStringRenderer *mShapedRenderer; 88 | 89 | private: 90 | Ui::MainWindow *ui; 91 | }; 92 | 93 | #endif // MAINWINDOW_H 94 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 0 25 | 0 26 | 27 | 28 | 29 | true 30 | 31 | 32 | QComboBox::AdjustToMinimumContentsLength 33 | 34 | 35 | 36 | 37 | 38 | 39 | Render 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 0 54 | 0 55 | 800 56 | 22 57 | 58 | 59 | 60 | 61 | &File 62 | 63 | 64 | 65 | 66 | 67 | &View 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 2 78 | 79 | 80 | 81 | 82 | 83 | 84 | true 85 | 86 | 87 | 88 | 89 | 0 90 | 0 91 | 332 92 | 1062 93 | 94 | 95 | 96 | 97 | 98 | 99 | Common 100 | 101 | 102 | 103 | 104 | 105 | Zoom 106 | 107 | 108 | 109 | 110 | 111 | 112 | 10 113 | 114 | 115 | 3000 116 | 117 | 118 | 1000 119 | 120 | 121 | Qt::Horizontal 122 | 123 | 124 | 125 | 126 | 127 | 128 | Max line width 129 | 130 | 131 | 132 | 133 | 134 | 135 | Qt::Horizontal 136 | 137 | 138 | 139 | 140 | 141 | 142 | Font path 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | true 151 | 152 | 153 | 154 | 155 | 156 | 157 | Set 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | Font size 170 | 171 | 172 | 173 | 174 | 175 | 176 | Font 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | Pen width 189 | 190 | 191 | 192 | 193 | 194 | 195 | 200 196 | 197 | 198 | 400 199 | 200 | 201 | 25 202 | 203 | 204 | 200 205 | 206 | 207 | 208 | 209 | 210 | 211 | 10 212 | 213 | 214 | 20 215 | 216 | 217 | 10 218 | 219 | 220 | 221 | 222 | 223 | 224 | Font color 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | View 242 | 243 | 244 | false 245 | 246 | 247 | 248 | 249 | 250 | Lines 251 | 252 | 253 | true 254 | 255 | 256 | 257 | 258 | 259 | 260 | Char Types 261 | 262 | 263 | true 264 | 265 | 266 | 267 | 268 | 269 | 270 | Levels 271 | 272 | 273 | true 274 | 275 | 276 | 277 | 278 | 279 | 280 | Scripts 281 | 282 | 283 | true 284 | 285 | 286 | 287 | 288 | 289 | 290 | Runs 291 | 292 | 293 | true 294 | 295 | 296 | 297 | 298 | 299 | 300 | Geometries 301 | 302 | 303 | true 304 | 305 | 306 | 307 | 308 | 309 | 310 | Code Points 311 | 312 | 313 | true 314 | 315 | 316 | 317 | 318 | 319 | 320 | Indices 321 | 322 | 323 | true 324 | 325 | 326 | 327 | 328 | 329 | 330 | Glyph Indices 331 | 332 | 333 | true 334 | 335 | 336 | 337 | 338 | 339 | 340 | Reordered Indices 341 | 342 | 343 | true 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | Analysis 354 | 355 | 356 | false 357 | 358 | 359 | 360 | 361 | 362 | Break runs on level change 363 | 364 | 365 | true 366 | 367 | 368 | 369 | 370 | 371 | 372 | Resolve HB_SCRIPT_COMMON 373 | and HB_SCRIPT_INHERITED 374 | 375 | 376 | true 377 | 378 | 379 | 380 | 381 | 382 | 383 | Paragraph base direction 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | LTR 392 | 393 | 394 | false 395 | 396 | 397 | 398 | 399 | 400 | 401 | RTL 402 | 403 | 404 | 405 | 406 | 407 | 408 | Auto 409 | 410 | 411 | true 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | Shaping 427 | 428 | 429 | false 430 | 431 | 432 | 433 | 434 | 435 | Shape with HarfBuzz 436 | 437 | 438 | true 439 | 440 | 441 | 442 | 443 | 444 | 445 | Shape with FriBidi 446 | 447 | 448 | 449 | 450 | 451 | 452 | false 453 | 454 | 455 | Remove zero-width invisible characters 456 | 457 | 458 | true 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | Rendering 469 | 470 | 471 | 472 | 473 | 474 | Levels 475 | 476 | 477 | true 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | Paragraph Base Direction 494 | 495 | 496 | true 497 | 498 | 499 | 500 | 501 | 502 | 503 | Runs 504 | 505 | 506 | true 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | E&xit 532 | 533 | 534 | 535 | 536 | true 537 | 538 | 539 | true 540 | 541 | 542 | &Panel 543 | 544 | 545 | 546 | 547 | 548 | ColorPicker 549 | QLabel 550 |
colorpicker.h
551 |
552 | 553 | GraphicsView 554 | QGraphicsView 555 |
graphicsview.h
556 |
557 |
558 | 559 | 560 |
561 | -------------------------------------------------------------------------------- /resource.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | fonts/DejaVuSans.ttf 4 | samples.txt 5 | 6 | 7 | -------------------------------------------------------------------------------- /samples.txt: -------------------------------------------------------------------------------- 1 | لَاْ حَوْلَ وَلَاْ قُوَّةَ إِلَّاْ بِاْللهِ العَلِيِّ العَظِيْم 2 | When the Arabic letter Lam (ل) is followed by the letter Alif (ا), FriBidi substitutes the two with the ligature Lam+Alif (لا) and a zero-width space is inserted. 3 | سبب ظهور المربعات في النص العربي هو: When the Arabic letter Lam (ل) is followed by the letter Alif (ا), FriBidi substitutes the two with the ligature Lam+Alif (لا) and a zero-width space is inserted. 4 | سبب ظهور المربعات في النص العربي: ‪When the Arabic letter Lam (‫ل‬) is followed by the letter Alif (‫ا‬), FriBidi substitutes the two with the ligature Lam+Alif (‫لا‬) and a zero-width space is inserted.‬ 5 | Did you know that the surname of former French prime minister Jean-Marc Ayrault, when pronounced, sounds like a very obscene Arabic word (‫إيرو‬) which literally means “his dick”? That caused a problem for broadcasting corporations back then because, you see, an innocent statement in the news such as: “‫التقى جلالة الملك عبد الله رئيس الوزراء ‪Ayrault‬ وعقدا مؤتمراً صحفياً مشتركاً في القاعة ‪123‬في فندق ‪The Four Seasons Hotel‬مساء أمس‬” which literally means “His Majesty King Abdallah met with prime minister Ayrault, and they held a joint press conference in meeting room 123 in The Four Seasons Hotel yesterday evening” can be misunderstood to mean that the dick of His Majesty King Abdallah is actually a prime minister, and the two actually hold joint press conferences at times. Needless to say, broadcasting corporations allowed their newscasters to mispronounce the name as ‫إيرولت‬ or ‫إيغو‬. Which is kind of unfortunate. It would have been fun to watch the embarrasement of female newscasters as they were forced to read obscenities on national TV. 6 | ‪Did you know that the surname of former French prime minister Jean-Marc Ayrault, when pronounced, sounds like a very obscene Arabic word (‫إيرو‬) which literally means “his dick”? That caused a problem for broadcasting corporations back then because, you see, an innocent statement in the news such as: “‫التقى جلالة الملك عبد الله رئيس الوزراء ‪Ayrault‬ وعقدا مؤتمراً صحفياً مشتركاً في القاعة ‪123‬في فندق ‪The Four Seasons Hotel‬مساء أمس‬” which literally means “His Majesty King Abdallah met with prime minister Ayrault, and they held a joint press conference in meeting room 123 in The Four Seasons Hotel yesterday evening” can be misunderstood to mean that the dick of His Majesty King Abdallah is actually a prime minister, and the two actually hold joint press conferences at times. Needless to say, broadcasting corporations allowed their newscasters to mispronounce the name as ‫إيرولت‬ or ‫إيغو‬. Which is kind of unfortunate. It would have been fun to watch the embarrasement of female newscasters as they were forced to read obscenities on national TV.‬ 7 | Once upon a time in a land far, far away, an evil witch cast a spell whereby all text appeared right to left. ‮This wasn’t an easy thing to get used to, to people who were LTR oriented. And, alas, people could no longer read subtitles in movies because, video applications did not support RTL text. And to make matters worse, digits were still written LTR. For example: ‭10 + 20 = 30‬. See?‬ This is the curse of bi-directional text which you, lucky bastards, do not have to live with every day. 8 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salshaaban/BidiRenderer/40eaf873cd116d6e911ec1decccb1f508f13181f/screenshot.png --------------------------------------------------------------------------------