mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 04:19:12 +00:00
Merge branch 'harfbuzz' into new-theme
This commit is contained in:
commit
776566463b
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -42,3 +42,6 @@
|
||||
[submodule "third_party/cmark"]
|
||||
path = third_party/cmark
|
||||
url = https://github.com/aseprite/cmark.git
|
||||
[submodule "third_party/harfbuzz"]
|
||||
path = third_party/harfbuzz
|
||||
url = https://github.com/aseprite/harfbuzz.git
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Aseprite
|
||||
# Copyright (C) 2001-2016 David Capello
|
||||
# Copyright (C) 2001-2017 David Capello
|
||||
#
|
||||
# Parts of this file come from the Allegro 4.4 CMakeLists.txt
|
||||
|
||||
@ -143,6 +143,7 @@ set(LOADPNG_DIR ${CMAKE_SOURCE_DIR}/third_party/loadpng)
|
||||
set(LIBWEBP_DIR ${CMAKE_SOURCE_DIR}/third_party/libwebp)
|
||||
set(PIXMAN_DIR ${CMAKE_SOURCE_DIR}/third_party/pixman)
|
||||
set(FREETYPE_DIR ${CMAKE_SOURCE_DIR}/third_party/freetype2)
|
||||
set(HARFBUZZ_DIR ${CMAKE_SOURCE_DIR}/third_party/harfbuzz)
|
||||
set(SIMPLEINI_DIR ${CMAKE_SOURCE_DIR}/third_party/simpleini)
|
||||
set(TINYXML_DIR ${CMAKE_SOURCE_DIR}/third_party/tinyxml)
|
||||
set(ZLIB_DIR ${CMAKE_SOURCE_DIR}/third_party/zlib)
|
||||
@ -263,6 +264,10 @@ else()
|
||||
endif()
|
||||
include_directories(${FREETYPE_INCLUDE_DIRS})
|
||||
|
||||
# harfbuzz
|
||||
set(HARFBUZZ_LIBRARIES harfbuzz)
|
||||
set(HARFBUZZ_INCLUDE_DIRS ${HARFBUZZ_DIR}/src)
|
||||
|
||||
if(USE_SHARED_GIFLIB)
|
||||
find_package(GIF REQUIRED)
|
||||
else()
|
||||
|
@ -486,6 +486,47 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
# [harfbuzz](http://harfbuzz.org)
|
||||
|
||||
```
|
||||
HarfBuzz is licensed under the so-called "Old MIT" license. Details follow.
|
||||
For parts of HarfBuzz that are licensed under different licenses see individual
|
||||
files names COPYING in subdirectories where applicable.
|
||||
|
||||
Copyright © 2010,2011,2012 Google, Inc.
|
||||
Copyright © 2012 Mozilla Foundation
|
||||
Copyright © 2011 Codethink Limited
|
||||
Copyright © 2008,2010 Nokia Corporation and/or its subsidiary(-ies)
|
||||
Copyright © 2009 Keith Stribley
|
||||
Copyright © 2009 Martin Hosken and SIL International
|
||||
Copyright © 2007 Chris Wilson
|
||||
Copyright © 2006 Behdad Esfahbod
|
||||
Copyright © 2005 David Turner
|
||||
Copyright © 2004,2007,2008,2009,2010 Red Hat, Inc.
|
||||
Copyright © 1998-2004 David Turner and Werner Lemberg
|
||||
|
||||
For full copyright notices consult the individual files in the package.
|
||||
|
||||
|
||||
Permission is hereby granted, without written agreement and without
|
||||
license or royalty fees, to use, copy, modify, and distribute this
|
||||
software and its documentation for any purpose, provided that the
|
||||
above copyright notice and the following two paragraphs appear in
|
||||
all copies of this software.
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
|
||||
THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
```
|
||||
|
||||
# [libjpeg](http://www.ijg.org/)
|
||||
|
||||
```
|
||||
@ -884,6 +925,24 @@ and releases new versions, with the help of Yves Berquin, Andrew
|
||||
Ellerton, and the tinyXml community.
|
||||
```
|
||||
|
||||
# [ucdn](https://github.com/grigorig/ucdn)
|
||||
|
||||
```
|
||||
Copyright (C) 2012 Grigori Goronzy <greg@kinoho.net>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
```
|
||||
|
||||
# [Wintab API](http://www.wacomeng.com/windows/docs/WintabBackground.htm)
|
||||
|
||||
```
|
||||
|
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit 2630b895fa15b1ff863d47d4ca0ad12e93c22fa9
|
||||
Subproject commit f232eac37ef0c8a0f5324ed3154b314c64b9eb13
|
@ -517,7 +517,8 @@ target_link_libraries(app-lib
|
||||
${PNG_LIBRARIES}
|
||||
${WEBP_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
${FREETYPE_LIBRARIES})
|
||||
${FREETYPE_LIBRARIES}
|
||||
${HARFBUZZ_LIBRARIES})
|
||||
|
||||
if(ENABLE_SCRIPTING)
|
||||
target_link_libraries(app-lib script-lib)
|
||||
|
@ -997,16 +997,10 @@ public:
|
||||
bool caretDrawn() const { return m_caretDrawn; }
|
||||
const gfx::Rect& textBounds() const { return m_textBounds; }
|
||||
|
||||
void preProcessChar(const base::utf8_const_iterator& it,
|
||||
const base::utf8_const_iterator& end,
|
||||
int& chr,
|
||||
void preProcessChar(const int index,
|
||||
const int codepoint,
|
||||
gfx::Color& fg,
|
||||
gfx::Color& bg,
|
||||
bool& drawChar,
|
||||
bool& moveCaret) override {
|
||||
if (m_widget->isPassword())
|
||||
chr = '*';
|
||||
|
||||
gfx::Color& bg) override {
|
||||
// Normal text
|
||||
auto& colors = SkinTheme::instance()->colors;
|
||||
bg = ColorNone;
|
||||
@ -1028,8 +1022,6 @@ public:
|
||||
fg = colors.disabled();
|
||||
}
|
||||
|
||||
drawChar = true;
|
||||
moveCaret = true;
|
||||
m_bg = bg;
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ class StatusBar::Indicators : public HBox {
|
||||
Graphics* g = ev.graphics();
|
||||
|
||||
g->fillRect(bgColor(), rc);
|
||||
if (textLength() > 0) {
|
||||
if (!text().empty()) {
|
||||
g->drawText(text(), textColor, ColorNone,
|
||||
Point(rc.x, rc.y + rc.h/2 - font()->height()/2));
|
||||
}
|
||||
|
@ -16,7 +16,9 @@
|
||||
#include "doc/color.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "ft/algorithm.h"
|
||||
#include "ft/face.h"
|
||||
#include "ft/hb_shaper.h"
|
||||
#include "ft/lib.h"
|
||||
|
||||
#include <stdexcept>
|
||||
@ -46,52 +48,51 @@ doc::Image* render_text(const std::string& fontfile, int fontsize,
|
||||
doc::clear_image(image, 0);
|
||||
|
||||
ft::ForEachGlyph<ft::Face> feg(face);
|
||||
auto it = base::utf8_const_iterator(text.begin());
|
||||
auto end = base::utf8_const_iterator(text.end());
|
||||
while (it != end) {
|
||||
feg.processChar(
|
||||
*it,
|
||||
[&bounds, &image, color, antialias](const ft::Glyph& glyph) {
|
||||
int t, yimg = - bounds.y + int(glyph.y);
|
||||
if (feg.initialize(base::utf8_const_iterator(text.begin()),
|
||||
base::utf8_const_iterator(text.end()))) {
|
||||
do {
|
||||
auto glyph = feg.glyph();
|
||||
if (!glyph)
|
||||
continue;
|
||||
|
||||
for (int v=0; v<int(glyph.bitmap->rows); ++v, ++yimg) {
|
||||
const uint8_t* p = glyph.bitmap->buffer + v*glyph.bitmap->pitch;
|
||||
int ximg = - bounds.x + int(glyph.x);
|
||||
int bit = 0;
|
||||
int t, yimg = - bounds.y + int(glyph->y);
|
||||
|
||||
for (int u=0; u<int(glyph.bitmap->width); ++u, ++ximg) {
|
||||
int alpha;
|
||||
for (int v=0; v<int(glyph->bitmap->rows); ++v, ++yimg) {
|
||||
const uint8_t* p = glyph->bitmap->buffer + v*glyph->bitmap->pitch;
|
||||
int ximg = - bounds.x + int(glyph->x);
|
||||
int bit = 0;
|
||||
|
||||
if (antialias) {
|
||||
alpha = *(p++);
|
||||
}
|
||||
else {
|
||||
alpha = ((*p) & (1 << (7 - (bit++))) ? 255: 0);
|
||||
if (bit == 8) {
|
||||
bit = 0;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
for (int u=0; u<int(glyph->bitmap->width); ++u, ++ximg) {
|
||||
int alpha;
|
||||
|
||||
int output_alpha = MUL_UN8(doc::rgba_geta(color), alpha, t);
|
||||
if (output_alpha) {
|
||||
doc::color_t output_color =
|
||||
doc::rgba(doc::rgba_getr(color),
|
||||
doc::rgba_getg(color),
|
||||
doc::rgba_getb(color),
|
||||
output_alpha);
|
||||
|
||||
doc::put_pixel(
|
||||
image, ximg, yimg,
|
||||
doc::rgba_blender_normal(
|
||||
doc::get_pixel(image, ximg, yimg),
|
||||
output_color));
|
||||
if (antialias) {
|
||||
alpha = *(p++);
|
||||
}
|
||||
else {
|
||||
alpha = ((*p) & (1 << (7 - (bit++))) ? 255: 0);
|
||||
if (bit == 8) {
|
||||
bit = 0;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
++it;
|
||||
int output_alpha = MUL_UN8(doc::rgba_geta(color), alpha, t);
|
||||
if (output_alpha) {
|
||||
doc::color_t output_color =
|
||||
doc::rgba(doc::rgba_getr(color),
|
||||
doc::rgba_getg(color),
|
||||
doc::rgba_getb(color),
|
||||
output_alpha);
|
||||
|
||||
doc::put_pixel(
|
||||
image, ximg, yimg,
|
||||
doc::rgba_blender_normal(
|
||||
doc::get_pixel(image, ximg, yimg),
|
||||
output_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (feg.nextChar());
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2016 David Capello
|
||||
Copyright (c) 2016-2017 David Capello
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Aseprite FreeType Wrapper
|
||||
*Copyright (C) 2016 David Capello*
|
||||
*Copyright (C) 2016-2017 David Capello*
|
||||
|
||||
> Distributed under [MIT license](LICENSE.txt)
|
||||
|
175
src/ft/algorithm.h
Normal file
175
src/ft/algorithm.h
Normal file
@ -0,0 +1,175 @@
|
||||
// Aseprite FreeType Wrapper
|
||||
// Copyright (c) 2016-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef FT_ALGORITHM_H_INCLUDED
|
||||
#define FT_ALGORITHM_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "base/string.h"
|
||||
#include "ft/freetype_headers.h"
|
||||
#include "ft/hb_shaper.h"
|
||||
#include "gfx/rect.h"
|
||||
|
||||
namespace ft {
|
||||
|
||||
template<typename FaceFT>
|
||||
class DefaultShaper {
|
||||
public:
|
||||
typedef typename FaceFT::Glyph Glyph;
|
||||
|
||||
DefaultShaper(FaceFT& face) : m_face(face) {
|
||||
}
|
||||
|
||||
bool initialize(const base::utf8_const_iterator& it,
|
||||
const base::utf8_const_iterator& end) {
|
||||
m_begin = m_it = it;
|
||||
m_end = end;
|
||||
return (m_it != end);
|
||||
}
|
||||
|
||||
bool nextChar() {
|
||||
++m_it;
|
||||
return (m_it != m_end);
|
||||
}
|
||||
|
||||
int unicodeChar() const {
|
||||
return *m_it;
|
||||
}
|
||||
|
||||
int charIndex() {
|
||||
return m_it - m_begin;
|
||||
}
|
||||
|
||||
unsigned int glyphIndex() {
|
||||
return m_face.cache().getGlyphIndex(m_face, unicodeChar());
|
||||
}
|
||||
|
||||
void glyphOffsetXY(Glyph* glyph) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
void glyphAdvanceXY(const Glyph* glyph, double& x, double& y) {
|
||||
x += glyph->ft_glyph->advance.x / double(1 << 16);
|
||||
y += glyph->ft_glyph->advance.y / double(1 << 16);
|
||||
}
|
||||
|
||||
private:
|
||||
FaceFT& m_face;
|
||||
base::utf8_const_iterator m_begin, m_end, m_it;
|
||||
};
|
||||
|
||||
template<typename FaceFT,
|
||||
typename Shaper = HBShaper<FaceFT> >
|
||||
class ForEachGlyph {
|
||||
public:
|
||||
typedef typename FaceFT::Glyph Glyph;
|
||||
|
||||
ForEachGlyph(FaceFT& face)
|
||||
: m_face(face)
|
||||
, m_shaper(face)
|
||||
, m_glyph(nullptr)
|
||||
, m_useKerning(FT_HAS_KERNING((FT_Face)face) ? true: false)
|
||||
, m_prevGlyph(0)
|
||||
, m_x(0.0), m_y(0.0) {
|
||||
}
|
||||
|
||||
~ForEachGlyph() {
|
||||
unloadGlyph();
|
||||
}
|
||||
|
||||
int unicodeChar() { return m_shaper.unicodeChar(); }
|
||||
int charIndex() { return m_shaper.charIndex(); }
|
||||
|
||||
const Glyph* glyph() const { return m_glyph; }
|
||||
|
||||
bool initialize(const base::utf8_const_iterator& it,
|
||||
const base::utf8_const_iterator& end) {
|
||||
bool res = m_shaper.initialize(it, end);
|
||||
if (res)
|
||||
prepareGlyph();
|
||||
return res;
|
||||
}
|
||||
|
||||
bool nextChar() {
|
||||
m_prevGlyph = m_shaper.glyphIndex();
|
||||
|
||||
if (m_shaper.nextChar()) {
|
||||
prepareGlyph();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void prepareGlyph() {
|
||||
FT_UInt glyphIndex = m_shaper.glyphIndex();
|
||||
double initialX = m_x;
|
||||
|
||||
if (m_useKerning && m_prevGlyph && glyphIndex) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(m_face, m_prevGlyph, glyphIndex,
|
||||
FT_KERNING_DEFAULT, &kerning);
|
||||
m_x += kerning.x / 64.0;
|
||||
}
|
||||
|
||||
unloadGlyph();
|
||||
|
||||
// Load new glyph
|
||||
m_glyph = m_face.cache().loadGlyph(m_face, glyphIndex, m_face.antialias());
|
||||
if (m_glyph) {
|
||||
m_glyph->bitmap = &FT_BitmapGlyph(m_glyph->ft_glyph)->bitmap;
|
||||
m_glyph->x = m_x
|
||||
+ m_glyph->bearingX;
|
||||
m_glyph->y = m_y
|
||||
+ m_face.height()
|
||||
+ m_face.descender() // descender is negative
|
||||
- m_glyph->bearingY;
|
||||
|
||||
m_shaper.glyphOffsetXY(m_glyph);
|
||||
m_shaper.glyphAdvanceXY(m_glyph, m_x, m_y);
|
||||
|
||||
m_glyph->startX = initialX;
|
||||
m_glyph->endX = m_x;
|
||||
}
|
||||
}
|
||||
|
||||
void unloadGlyph() {
|
||||
if (m_glyph) {
|
||||
m_face.cache().doneGlyph(m_glyph);
|
||||
m_glyph = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
FaceFT& m_face;
|
||||
Shaper m_shaper;
|
||||
Glyph* m_glyph;
|
||||
bool m_useKerning;
|
||||
FT_UInt m_prevGlyph;
|
||||
double m_x, m_y;
|
||||
};
|
||||
|
||||
template<typename FaceFT>
|
||||
gfx::Rect calc_text_bounds(FaceFT& face, const std::string& str) {
|
||||
gfx::Rect bounds(0, 0, 0, 0);
|
||||
ForEachGlyph<FaceFT> feg(face);
|
||||
if (feg.initialize(base::utf8_const_iterator(str.begin()),
|
||||
base::utf8_const_iterator(str.end()))) {
|
||||
do {
|
||||
if (auto glyph = feg.glyph())
|
||||
bounds |= gfx::Rect(int(glyph->x),
|
||||
int(glyph->y),
|
||||
glyph->bitmap->width,
|
||||
glyph->bitmap->rows);
|
||||
} while (feg.nextChar());
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
} // namespace ft
|
||||
|
||||
#endif
|
@ -10,9 +10,7 @@
|
||||
|
||||
#include "base/debug.h"
|
||||
#include "base/disable_copying.h"
|
||||
#include "base/string.h"
|
||||
#include "ft/freetype_headers.h"
|
||||
#include "gfx/rect.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
@ -33,6 +31,8 @@ namespace ft {
|
||||
template<typename Cache>
|
||||
class FaceFT {
|
||||
public:
|
||||
typedef ft::Glyph Glyph;
|
||||
|
||||
FaceFT(FT_Face face) : m_face(face) {
|
||||
}
|
||||
|
||||
@ -94,79 +94,6 @@ namespace ft {
|
||||
DISABLE_COPYING(FaceFT);
|
||||
};
|
||||
|
||||
template<typename FaceFT>
|
||||
class ForEachGlyph {
|
||||
public:
|
||||
ForEachGlyph(FaceFT& face)
|
||||
: m_face(face)
|
||||
, m_useKerning(FT_HAS_KERNING((FT_Face)face) ? true: false)
|
||||
, m_prevGlyph(0)
|
||||
, m_x(0.0), m_y(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename ProcessGlyph>
|
||||
void processChar(const int chr, ProcessGlyph processGlyph) {
|
||||
FT_UInt glyphIndex = m_face.cache().getGlyphIndex(m_face, chr);
|
||||
double initialX = m_x;
|
||||
|
||||
if (m_useKerning && m_prevGlyph && glyphIndex) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(m_face, m_prevGlyph, glyphIndex,
|
||||
FT_KERNING_DEFAULT, &kerning);
|
||||
m_x += kerning.x / 64.0;
|
||||
}
|
||||
|
||||
Glyph* glyph = m_face.cache().loadGlyph(
|
||||
m_face, glyphIndex, m_face.antialias());
|
||||
if (glyph) {
|
||||
glyph->bitmap = &FT_BitmapGlyph(glyph->ft_glyph)->bitmap;
|
||||
glyph->x = m_x + glyph->bearingX;
|
||||
glyph->y = m_y
|
||||
+ m_face.height()
|
||||
+ m_face.descender() // descender is negative
|
||||
- glyph->bearingY;
|
||||
|
||||
m_x += glyph->ft_glyph->advance.x / double(1 << 16);
|
||||
m_y += glyph->ft_glyph->advance.y / double(1 << 16);
|
||||
|
||||
glyph->startX = initialX;
|
||||
glyph->endX = m_x;
|
||||
|
||||
processGlyph(*glyph);
|
||||
|
||||
m_face.cache().doneGlyph(glyph);
|
||||
}
|
||||
|
||||
m_prevGlyph = glyphIndex;
|
||||
}
|
||||
|
||||
private:
|
||||
FaceFT& m_face;
|
||||
bool m_useKerning;
|
||||
FT_UInt m_prevGlyph;
|
||||
double m_x, m_y;
|
||||
};
|
||||
|
||||
template<typename FaceFT>
|
||||
gfx::Rect calc_text_bounds(FaceFT& face, const std::string& str) {
|
||||
gfx::Rect bounds(0, 0, 0, 0);
|
||||
auto it = base::utf8_const_iterator(str.begin());
|
||||
auto end = base::utf8_const_iterator(str.end());
|
||||
ForEachGlyph<FaceFT> feg(face);
|
||||
for (; it != end; ++it) {
|
||||
feg.processChar(
|
||||
*it,
|
||||
[&bounds](Glyph& glyph) {
|
||||
bounds |= gfx::Rect(int(glyph.x),
|
||||
int(glyph.y),
|
||||
glyph.bitmap->width,
|
||||
glyph.bitmap->rows);
|
||||
});
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
class NoCache {
|
||||
public:
|
||||
void invalidate() {
|
||||
@ -261,8 +188,6 @@ namespace ft {
|
||||
std::map<FT_UInt, Glyph*> m_glyphMap;
|
||||
};
|
||||
|
||||
typedef FaceFT<SimpleCache> Face;
|
||||
|
||||
} // namespace ft
|
||||
|
||||
#endif
|
||||
|
44
src/ft/hb_face.h
Normal file
44
src/ft/hb_face.h
Normal file
@ -0,0 +1,44 @@
|
||||
// Aseprite FreeType Wrapper
|
||||
// Copyright (c) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef FT_HB_FACE_H_INCLUDED
|
||||
#define FT_HB_FACE_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "base/string.h"
|
||||
#include "ft/face.h"
|
||||
|
||||
#include <hb.h>
|
||||
#include <hb-ft.h>
|
||||
|
||||
namespace ft {
|
||||
|
||||
template<typename FaceFT>
|
||||
class HBFace : public FaceFT {
|
||||
public:
|
||||
HBFace(FT_Face face) : FaceFT(face) {
|
||||
m_font = hb_ft_font_create((FT_Face)face, nullptr);
|
||||
m_buffer = hb_buffer_create();
|
||||
}
|
||||
|
||||
~HBFace() {
|
||||
if (m_buffer) hb_buffer_destroy(m_buffer);
|
||||
if (m_font) hb_font_destroy(m_font);
|
||||
}
|
||||
|
||||
hb_font_t* font() const { return m_font; }
|
||||
hb_buffer_t* buffer() const { return m_buffer; }
|
||||
|
||||
private:
|
||||
hb_buffer_t* m_buffer;
|
||||
hb_font_t* m_font;
|
||||
};
|
||||
|
||||
typedef HBFace<FaceFT<SimpleCache> > Face;
|
||||
|
||||
} // namespace ft
|
||||
|
||||
#endif
|
101
src/ft/hb_shaper.h
Normal file
101
src/ft/hb_shaper.h
Normal file
@ -0,0 +1,101 @@
|
||||
// Aseprite FreeType Wrapper
|
||||
// Copyright (c) 2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef FT_HB_SHAPER_H_INCLUDED
|
||||
#define FT_HB_SHAPER_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "ft/hb_face.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ft {
|
||||
|
||||
template<typename HBFace>
|
||||
class HBShaper {
|
||||
public:
|
||||
HBShaper(HBFace& face)
|
||||
: m_face(face)
|
||||
, m_unicodeFuncs(hb_buffer_get_unicode_funcs(face.buffer())) {
|
||||
}
|
||||
|
||||
bool initialize(const base::utf8_const_iterator& it,
|
||||
const base::utf8_const_iterator& end) {
|
||||
m_begin = m_it = it;
|
||||
m_end = end;
|
||||
m_index = 0;
|
||||
|
||||
hb_buffer_t* buf = m_face.buffer();
|
||||
|
||||
hb_buffer_reset(buf);
|
||||
for (auto it=m_it; it!=end; ++it)
|
||||
hb_buffer_add(buf, *it, 0);
|
||||
hb_buffer_set_content_type(buf, HB_BUFFER_CONTENT_TYPE_UNICODE);
|
||||
hb_buffer_set_direction(buf, HB_DIRECTION_LTR);
|
||||
hb_buffer_guess_segment_properties(buf);
|
||||
|
||||
hb_shape(m_face.font(), buf, nullptr, 0);
|
||||
|
||||
m_glyphInfo = hb_buffer_get_glyph_infos(buf, &m_glyphCount);
|
||||
m_glyphPos = hb_buffer_get_glyph_positions(buf, &m_glyphCount);
|
||||
return (m_glyphCount > 0);
|
||||
}
|
||||
|
||||
bool nextChar() {
|
||||
advanceIterator(m_it);
|
||||
++m_index;
|
||||
return (m_index < m_glyphCount);
|
||||
}
|
||||
|
||||
int unicodeChar() const {
|
||||
auto it = m_it;
|
||||
return advanceIterator(it);
|
||||
}
|
||||
|
||||
int charIndex() {
|
||||
return m_it - m_begin;
|
||||
}
|
||||
|
||||
unsigned int glyphIndex() const {
|
||||
return m_glyphInfo[m_index].codepoint;
|
||||
}
|
||||
|
||||
void glyphOffsetXY(Glyph* glyph) {
|
||||
glyph->x += m_glyphPos[m_index].x_offset / 64.0;
|
||||
glyph->y += m_glyphPos[m_index].y_offset / 64.0;
|
||||
}
|
||||
|
||||
void glyphAdvanceXY(const Glyph* glyph, double& x, double& y) {
|
||||
x += m_glyphPos[m_index].x_advance / 64.0;
|
||||
y += m_glyphPos[m_index].y_advance / 64.0;
|
||||
}
|
||||
|
||||
private:
|
||||
hb_codepoint_t advanceIterator(base::utf8_const_iterator& it) const {
|
||||
hb_codepoint_t chr = *it;
|
||||
hb_codepoint_t newChr = 0;
|
||||
++it;
|
||||
while (it != m_end) {
|
||||
if (!hb_unicode_compose(m_unicodeFuncs, chr, *it, &newChr))
|
||||
break;
|
||||
chr = newChr;
|
||||
++it;
|
||||
}
|
||||
return chr;
|
||||
}
|
||||
|
||||
HBFace& m_face;
|
||||
hb_glyph_info_t* m_glyphInfo;
|
||||
hb_glyph_position_t* m_glyphPos;
|
||||
unsigned int m_glyphCount;
|
||||
int m_index;
|
||||
base::utf8_const_iterator m_begin, m_end, m_it;
|
||||
hb_unicode_funcs_t* m_unicodeFuncs;
|
||||
};
|
||||
|
||||
} // namespace ft
|
||||
|
||||
#endif
|
@ -11,6 +11,7 @@
|
||||
#include "she/common/freetype_font.h"
|
||||
|
||||
#include "base/string.h"
|
||||
#include "ft/algorithm.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/size.h"
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
#define SHE_COMMON_FREETYPE_FONT_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "ft/face.h"
|
||||
#include "ft/hb_face.h"
|
||||
#include "ft/lib.h"
|
||||
#include "she/font.h"
|
||||
|
||||
@ -17,6 +17,8 @@ namespace she {
|
||||
|
||||
class FreeTypeFont : public Font {
|
||||
public:
|
||||
typedef ft::Face Face;
|
||||
|
||||
FreeTypeFont(const char* filename, int height);
|
||||
~FreeTypeFont();
|
||||
|
||||
@ -29,11 +31,11 @@ namespace she {
|
||||
void setSize(int size) override;
|
||||
void setAntialias(bool antialias) override;
|
||||
|
||||
ft::Face& face() { return m_face; }
|
||||
Face& face() { return m_face; }
|
||||
|
||||
private:
|
||||
mutable ft::Lib m_ft;
|
||||
mutable ft::Face m_face;
|
||||
mutable Face m_face;
|
||||
};
|
||||
|
||||
FreeTypeFont* loadFreeTypeFont(const char* filename, int height);
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
#include "she/draw_text.h"
|
||||
|
||||
#include "ft/algorithm.h"
|
||||
#include "ft/hb_shaper.h"
|
||||
#include "gfx/clip.h"
|
||||
#include "she/common/freetype_font.h"
|
||||
#include "she/common/generic_surface.h"
|
||||
@ -18,15 +20,14 @@
|
||||
namespace she {
|
||||
|
||||
gfx::Rect draw_text(Surface* surface, Font* font,
|
||||
base::utf8_const_iterator it,
|
||||
const base::utf8_const_iterator& begin,
|
||||
const base::utf8_const_iterator& end,
|
||||
gfx::Color fg, gfx::Color bg,
|
||||
int x, int y,
|
||||
DrawTextDelegate* delegate)
|
||||
{
|
||||
base::utf8_const_iterator it = begin;
|
||||
gfx::Rect textBounds;
|
||||
bool drawChar = true;
|
||||
bool moveCaret = true;
|
||||
|
||||
switch (font->type()) {
|
||||
|
||||
@ -34,30 +35,29 @@ gfx::Rect draw_text(Surface* surface, Font* font,
|
||||
SpriteSheetFont* ssFont = static_cast<SpriteSheetFont*>(font);
|
||||
while (it != end) {
|
||||
int chr = *it;
|
||||
if (delegate)
|
||||
delegate->preProcessChar(it, end, chr, fg, bg, drawChar, moveCaret);
|
||||
|
||||
if (moveCaret) {
|
||||
gfx::Rect charBounds = ssFont->getCharBounds(chr);
|
||||
gfx::Rect outCharBounds(x, y, charBounds.w, charBounds.h);
|
||||
if (delegate && !delegate->preDrawChar(outCharBounds))
|
||||
break;
|
||||
|
||||
if (!charBounds.isEmpty()) {
|
||||
if (surface && drawChar) {
|
||||
Surface* sheet = ssFont->getSurfaceSheet();
|
||||
SurfaceLock lock(sheet);
|
||||
surface->drawColoredRgbaSurface(sheet, fg, bg, gfx::Clip(x, y, charBounds));
|
||||
}
|
||||
}
|
||||
|
||||
textBounds |= outCharBounds;
|
||||
if (delegate)
|
||||
delegate->postDrawChar(outCharBounds);
|
||||
|
||||
x += charBounds.w;
|
||||
if (delegate) {
|
||||
int i = it-begin;
|
||||
delegate->preProcessChar(i, chr, fg, bg);
|
||||
}
|
||||
|
||||
gfx::Rect charBounds = ssFont->getCharBounds(chr);
|
||||
gfx::Rect outCharBounds(x, y, charBounds.w, charBounds.h);
|
||||
if (delegate && !delegate->preDrawChar(outCharBounds))
|
||||
break;
|
||||
|
||||
if (!charBounds.isEmpty()) {
|
||||
if (surface) {
|
||||
Surface* sheet = ssFont->getSurfaceSheet();
|
||||
SurfaceLock lock(sheet);
|
||||
surface->drawColoredRgbaSurface(sheet, fg, bg, gfx::Clip(x, y, charBounds));
|
||||
}
|
||||
}
|
||||
|
||||
textBounds |= outCharBounds;
|
||||
if (delegate)
|
||||
delegate->postDrawChar(outCharBounds);
|
||||
|
||||
x += charBounds.w;
|
||||
++it;
|
||||
}
|
||||
break;
|
||||
@ -66,7 +66,6 @@ gfx::Rect draw_text(Surface* surface, Font* font,
|
||||
case FontType::kTrueType: {
|
||||
FreeTypeFont* ttFont = static_cast<FreeTypeFont*>(font);
|
||||
bool antialias = ttFont->face().antialias();
|
||||
bool done = false;
|
||||
int fg_alpha = gfx::geta(fg);
|
||||
|
||||
gfx::Rect clipBounds;
|
||||
@ -76,119 +75,109 @@ gfx::Rect draw_text(Surface* surface, Font* font,
|
||||
surface->getFormat(&fd);
|
||||
}
|
||||
|
||||
ft::ForEachGlyph<ft::Face> feg(ttFont->face());
|
||||
while (it != end) {
|
||||
int chr = *it;
|
||||
if (delegate)
|
||||
delegate->preProcessChar(it, end, chr, fg, bg, drawChar, moveCaret);
|
||||
ft::ForEachGlyph<FreeTypeFont::Face> feg(ttFont->face());
|
||||
if (feg.initialize(it, end)) {
|
||||
do {
|
||||
if (delegate) {
|
||||
delegate->preProcessChar(feg.charIndex(),
|
||||
feg.unicodeChar(), fg, bg);
|
||||
}
|
||||
|
||||
if (!moveCaret) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
auto glyph = feg.glyph();
|
||||
if (!glyph)
|
||||
continue;
|
||||
|
||||
feg.processChar(
|
||||
chr,
|
||||
[x, y, fg, fg_alpha, bg, antialias, surface,
|
||||
&clipBounds, &textBounds, &fd, &done, delegate, drawChar]
|
||||
(const ft::Glyph& glyph) {
|
||||
gfx::Rect origDstBounds(
|
||||
x + int(glyph.startX),
|
||||
y + int(glyph.y),
|
||||
int(glyph.endX) - int(glyph.startX),
|
||||
int(glyph.bitmap->rows) ? int(glyph.bitmap->rows): 1);
|
||||
gfx::Rect origDstBounds(
|
||||
x + int(glyph->startX),
|
||||
y + int(glyph->y),
|
||||
int(glyph->endX) - int(glyph->startX),
|
||||
int(glyph->bitmap->rows) ? int(glyph->bitmap->rows): 1);
|
||||
|
||||
if (delegate && !delegate->preDrawChar(origDstBounds)) {
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
origDstBounds.x = x + int(glyph.x);
|
||||
origDstBounds.w = int(glyph.bitmap->width);
|
||||
origDstBounds.h = int(glyph.bitmap->rows);
|
||||
if (delegate && !delegate->preDrawChar(origDstBounds))
|
||||
break;
|
||||
|
||||
gfx::Rect dstBounds = origDstBounds;
|
||||
if (surface)
|
||||
dstBounds &= clipBounds;
|
||||
origDstBounds.x = x + int(glyph->x);
|
||||
origDstBounds.w = int(glyph->bitmap->width);
|
||||
origDstBounds.h = int(glyph->bitmap->rows);
|
||||
|
||||
if (surface && drawChar && !dstBounds.isEmpty()) {
|
||||
int clippedRows = dstBounds.y - origDstBounds.y;
|
||||
int dst_y = dstBounds.y;
|
||||
int t;
|
||||
for (int v=0; v<dstBounds.h; ++v, ++dst_y) {
|
||||
int bit = 0;
|
||||
const uint8_t* p = glyph.bitmap->buffer
|
||||
+ (v+clippedRows)*glyph.bitmap->pitch;
|
||||
int dst_x = dstBounds.x;
|
||||
uint32_t* dst_address =
|
||||
(uint32_t*)surface->getData(dst_x, dst_y);
|
||||
gfx::Rect dstBounds = origDstBounds;
|
||||
if (surface)
|
||||
dstBounds &= clipBounds;
|
||||
|
||||
// Skip first clipped pixels
|
||||
for (int u=0; u<dstBounds.x-origDstBounds.x; ++u) {
|
||||
if (antialias) {
|
||||
if (surface && !dstBounds.isEmpty()) {
|
||||
int clippedRows = dstBounds.y - origDstBounds.y;
|
||||
int dst_y = dstBounds.y;
|
||||
int t;
|
||||
for (int v=0; v<dstBounds.h; ++v, ++dst_y) {
|
||||
int bit = 0;
|
||||
const uint8_t* p = glyph->bitmap->buffer
|
||||
+ (v+clippedRows)*glyph->bitmap->pitch;
|
||||
int dst_x = dstBounds.x;
|
||||
uint32_t* dst_address =
|
||||
(uint32_t*)surface->getData(dst_x, dst_y);
|
||||
|
||||
// Skip first clipped pixels
|
||||
for (int u=0; u<dstBounds.x-origDstBounds.x; ++u) {
|
||||
if (antialias) {
|
||||
++p;
|
||||
}
|
||||
else {
|
||||
if (bit == 8) {
|
||||
bit = 0;
|
||||
++p;
|
||||
}
|
||||
else {
|
||||
if (bit == 8) {
|
||||
bit = 0;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int u=0; u<dstBounds.w; ++u, ++dst_x) {
|
||||
ASSERT(clipBounds.contains(gfx::Point(dst_x, dst_y)));
|
||||
|
||||
int alpha;
|
||||
if (antialias) {
|
||||
alpha = *(p++);
|
||||
}
|
||||
else {
|
||||
alpha = ((*p) & (1 << (7 - (bit++))) ? 255: 0);
|
||||
if (bit == 8) {
|
||||
bit = 0;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t backdrop = *dst_address;
|
||||
gfx::Color backdropColor =
|
||||
gfx::rgba(
|
||||
((backdrop & fd.redMask) >> fd.redShift),
|
||||
((backdrop & fd.greenMask) >> fd.greenShift),
|
||||
((backdrop & fd.blueMask) >> fd.blueShift),
|
||||
((backdrop & fd.alphaMask) >> fd.alphaShift));
|
||||
|
||||
gfx::Color output = gfx::rgba(gfx::getr(fg),
|
||||
gfx::getg(fg),
|
||||
gfx::getb(fg),
|
||||
MUL_UN8(fg_alpha, alpha, t));
|
||||
if (gfx::geta(bg) > 0)
|
||||
output = blend(blend(backdropColor, bg), output);
|
||||
else
|
||||
output = blend(backdropColor, output);
|
||||
|
||||
*dst_address =
|
||||
((gfx::getr(output) << fd.redShift ) & fd.redMask ) |
|
||||
((gfx::getg(output) << fd.greenShift) & fd.greenMask) |
|
||||
((gfx::getb(output) << fd.blueShift ) & fd.blueMask ) |
|
||||
((gfx::geta(output) << fd.alphaShift) & fd.alphaMask);
|
||||
|
||||
++dst_address;
|
||||
}
|
||||
}
|
||||
|
||||
for (int u=0; u<dstBounds.w; ++u, ++dst_x) {
|
||||
ASSERT(clipBounds.contains(gfx::Point(dst_x, dst_y)));
|
||||
|
||||
int alpha;
|
||||
if (antialias) {
|
||||
alpha = *(p++);
|
||||
}
|
||||
else {
|
||||
alpha = ((*p) & (1 << (7 - (bit++))) ? 255: 0);
|
||||
if (bit == 8) {
|
||||
bit = 0;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t backdrop = *dst_address;
|
||||
gfx::Color backdropColor =
|
||||
gfx::rgba(
|
||||
((backdrop & fd.redMask) >> fd.redShift),
|
||||
((backdrop & fd.greenMask) >> fd.greenShift),
|
||||
((backdrop & fd.blueMask) >> fd.blueShift),
|
||||
((backdrop & fd.alphaMask) >> fd.alphaShift));
|
||||
|
||||
gfx::Color output = gfx::rgba(gfx::getr(fg),
|
||||
gfx::getg(fg),
|
||||
gfx::getb(fg),
|
||||
MUL_UN8(fg_alpha, alpha, t));
|
||||
if (gfx::geta(bg) > 0)
|
||||
output = blend(blend(backdropColor, bg), output);
|
||||
else
|
||||
output = blend(backdropColor, output);
|
||||
|
||||
*dst_address =
|
||||
((gfx::getr(output) << fd.redShift ) & fd.redMask ) |
|
||||
((gfx::getg(output) << fd.greenShift) & fd.greenMask) |
|
||||
((gfx::getb(output) << fd.blueShift ) & fd.blueMask ) |
|
||||
((gfx::geta(output) << fd.alphaShift) & fd.alphaMask);
|
||||
|
||||
++dst_address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!origDstBounds.w) origDstBounds.w = 1;
|
||||
if (!origDstBounds.h) origDstBounds.h = 1;
|
||||
textBounds |= origDstBounds;
|
||||
if (delegate)
|
||||
delegate->postDrawChar(origDstBounds);
|
||||
});
|
||||
|
||||
if (done)
|
||||
break;
|
||||
|
||||
++it;
|
||||
if (!origDstBounds.w) origDstBounds.w = 1;
|
||||
if (!origDstBounds.h) origDstBounds.h = 1;
|
||||
textBounds |= origDstBounds;
|
||||
if (delegate)
|
||||
delegate->postDrawChar(origDstBounds);
|
||||
} while (feg.nextChar());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -22,18 +22,10 @@ namespace she {
|
||||
public:
|
||||
virtual ~DrawTextDelegate() { }
|
||||
|
||||
// This is called before drawing the character. Here you can
|
||||
// modify the final painted character (e.g. for passwords you can
|
||||
// modify chr='*') and change the specific fg/bg color for this
|
||||
// char (e.g. to change the color depending if is a
|
||||
// selected/highlighted portion of text).
|
||||
virtual void preProcessChar(const base::utf8_const_iterator& it,
|
||||
const base::utf8_const_iterator& end,
|
||||
int& chr,
|
||||
gfx::Color& fg,
|
||||
gfx::Color& bg,
|
||||
bool& drawChar,
|
||||
bool& moveCaret) {
|
||||
// This is called before drawing the character.
|
||||
virtual void preProcessChar(const int index,
|
||||
const int codepoint,
|
||||
gfx::Color& fg, gfx::Color& bg) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@ -51,7 +43,7 @@ namespace she {
|
||||
// (e.g. measure how much space will use the text without drawing
|
||||
// it).
|
||||
gfx::Rect draw_text(Surface* surface, Font* font,
|
||||
base::utf8_const_iterator it,
|
||||
const base::utf8_const_iterator& begin,
|
||||
const base::utf8_const_iterator& end,
|
||||
gfx::Color fg, gfx::Color bg,
|
||||
int x, int y,
|
||||
|
@ -278,7 +278,7 @@ void ComboBox::setSelectedItemIndex(int itemIndex)
|
||||
ListItem* item = *it;
|
||||
m_entry->setText(item->text());
|
||||
if (isEditable())
|
||||
m_entry->selectText(m_entry->textLength(), m_entry->textLength());
|
||||
m_entry->setCaretToEnd();
|
||||
|
||||
onChange();
|
||||
}
|
||||
@ -394,7 +394,7 @@ void ComboBox::onSizeHint(SizeHintEvent& ev)
|
||||
Size entrySize = m_entry->sizeHint();
|
||||
Size reqSize = entrySize;
|
||||
|
||||
// Get the text-length of every item and put in 'w' the maximum value
|
||||
// Get the text-length of every item
|
||||
ListItems::iterator it, end = m_items.end();
|
||||
for (it = m_items.begin(); it != end; ++it) {
|
||||
int item_w =
|
||||
@ -498,7 +498,7 @@ bool ComboBoxEntry::onProcessMessage(Message* msg)
|
||||
// of the text. We don't select the whole text so the user can
|
||||
// delete the last caracters using backspace and complete the
|
||||
// item name.
|
||||
selectText(textLength(), textLength());
|
||||
setCaretToEnd();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
261
src/ui/entry.cpp
261
src/ui/entry.cpp
@ -30,25 +30,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
namespace {
|
||||
|
||||
class MeasureTextDelegate : public she::DrawTextDelegate {
|
||||
public:
|
||||
MeasureTextDelegate() { }
|
||||
|
||||
gfx::Rect bounds() const { return m_bounds; }
|
||||
|
||||
bool preDrawChar(const gfx::Rect& charBounds) override {
|
||||
m_bounds |= charBounds;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
gfx::Rect m_bounds;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Entry::Entry(const std::size_t maxsize, const char* format, ...)
|
||||
: Widget(kEntryWidget)
|
||||
, m_timer(500, this)
|
||||
@ -59,7 +40,6 @@ Entry::Entry(const std::size_t maxsize, const char* format, ...)
|
||||
, m_hidden(false)
|
||||
, m_state(false)
|
||||
, m_readonly(false)
|
||||
, m_password(false)
|
||||
, m_recent_focused(false)
|
||||
, m_lock_selection(false)
|
||||
, m_translate_dead_keys(true)
|
||||
@ -67,7 +47,7 @@ Entry::Entry(const std::size_t maxsize, const char* format, ...)
|
||||
enableFlags(CTRL_RIGHT_CLICK);
|
||||
|
||||
// formatted string
|
||||
char buf[4096];
|
||||
char buf[4096]; // TODO buffer overflow
|
||||
if (format) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
@ -101,21 +81,11 @@ bool Entry::isReadOnly() const
|
||||
return m_readonly;
|
||||
}
|
||||
|
||||
bool Entry::isPassword() const
|
||||
{
|
||||
return m_password;
|
||||
}
|
||||
|
||||
void Entry::setReadOnly(bool state)
|
||||
{
|
||||
m_readonly = state;
|
||||
}
|
||||
|
||||
void Entry::setPassword(bool state)
|
||||
{
|
||||
m_password = state;
|
||||
}
|
||||
|
||||
void Entry::showCaret()
|
||||
{
|
||||
m_hidden = false;
|
||||
@ -128,10 +98,15 @@ void Entry::hideCaret()
|
||||
invalidate();
|
||||
}
|
||||
|
||||
int Entry::lastCaretPos() const
|
||||
{
|
||||
return int(m_boxes.size()-1);
|
||||
}
|
||||
|
||||
void Entry::setCaretPos(int pos)
|
||||
{
|
||||
gfx::Size caretSize = theme()->getEntryCaretSize(this);
|
||||
int textlen = base::utf8_length(text());
|
||||
int textlen = lastCaretPos();
|
||||
m_caret = MID(0, pos, textlen);
|
||||
m_scroll = MID(0, m_scroll, textlen);
|
||||
|
||||
@ -140,21 +115,17 @@ void Entry::setCaretPos(int pos)
|
||||
m_scroll = m_caret;
|
||||
// Forward scroll
|
||||
else if (m_caret > m_scroll) {
|
||||
auto it = base::utf8_const_iterator(text().begin()) + m_scroll;
|
||||
int xLimit = bounds().x2() - border().right();
|
||||
while (m_caret > m_scroll) {
|
||||
MeasureTextDelegate delegate;
|
||||
she::draw_text(nullptr, font(), it, it+(m_caret-m_scroll),
|
||||
gfx::ColorNone, gfx::ColorNone, 0, 0,
|
||||
&delegate);
|
||||
int segmentWidth = 0;
|
||||
for (int j=m_scroll; j<m_caret; ++j)
|
||||
segmentWidth += m_boxes[j].width;
|
||||
|
||||
int x = bounds().x + border().left()
|
||||
+ delegate.bounds().w + caretSize.w;
|
||||
if (x < bounds().x2() - border().right())
|
||||
int x = bounds().x + border().left() + segmentWidth + caretSize.w;
|
||||
if (x < xLimit)
|
||||
break;
|
||||
else {
|
||||
++it;
|
||||
else
|
||||
++m_scroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,9 +135,15 @@ void Entry::setCaretPos(int pos)
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void Entry::setCaretToEnd()
|
||||
{
|
||||
int end = lastCaretPos();
|
||||
selectText(end, end);
|
||||
}
|
||||
|
||||
void Entry::selectText(int from, int to)
|
||||
{
|
||||
int end = base::utf8_length(text());
|
||||
int end = lastCaretPos();
|
||||
|
||||
m_select = from;
|
||||
setCaretPos(from); // to move scroll
|
||||
@ -186,6 +163,21 @@ void Entry::deselectText()
|
||||
invalidate();
|
||||
}
|
||||
|
||||
std::string Entry::selectedText() const
|
||||
{
|
||||
int selbeg, selend;
|
||||
getEntryThemeInfo(nullptr, nullptr, nullptr, &selbeg, &selend);
|
||||
|
||||
if (selbeg >= 0 && selend >= 0) {
|
||||
ASSERT(selbeg < int(m_boxes.size()));
|
||||
ASSERT(selend < int(m_boxes.size()));
|
||||
return text().substr(m_boxes[selbeg].from,
|
||||
m_boxes[selend].to - m_boxes[selbeg].from);
|
||||
}
|
||||
else
|
||||
return std::string();
|
||||
}
|
||||
|
||||
void Entry::setSuffix(const std::string& suffix)
|
||||
{
|
||||
m_suffix = suffix;
|
||||
@ -198,7 +190,7 @@ void Entry::setTranslateDeadKeys(bool state)
|
||||
}
|
||||
|
||||
void Entry::getEntryThemeInfo(int* scroll, int* caret, int* state,
|
||||
int* selbeg, int* selend)
|
||||
int* selbeg, int* selend) const
|
||||
{
|
||||
if (scroll) *scroll = m_scroll;
|
||||
if (caret) *caret = m_caret;
|
||||
@ -349,7 +341,7 @@ bool Entry::onProcessMessage(Message* msg)
|
||||
|
||||
// Select dead-key
|
||||
if (keymsg->isDeadKey()) {
|
||||
if (base::from_utf8(text()).size() < m_maxsize)
|
||||
if (lastCaretPos() < m_maxsize)
|
||||
selectText(m_caret-1, m_caret);
|
||||
}
|
||||
return true;
|
||||
@ -463,8 +455,9 @@ void Entry::onPaint(PaintEvent& ev)
|
||||
void Entry::onSetText()
|
||||
{
|
||||
Widget::onSetText();
|
||||
recalcCharBoxes(text());
|
||||
|
||||
int textlen = textLength();
|
||||
int textlen = lastCaretPos();
|
||||
if (m_caret >= 0 && m_caret > textlen)
|
||||
m_caret = textlen;
|
||||
}
|
||||
@ -492,16 +485,14 @@ int Entry::getCaretFromMouse(MouseMessage* mousemsg)
|
||||
return MAX(0, m_scroll-1);
|
||||
}
|
||||
|
||||
int textlen = base::utf8_length(text());
|
||||
auto it = base::utf8_const_iterator(text().begin()) + m_scroll;
|
||||
int i = MIN(m_scroll, textlen);
|
||||
for (; i<textlen; ++i) {
|
||||
MeasureTextDelegate delegate;
|
||||
she::draw_text(nullptr, font(), it, it+(i-m_scroll),
|
||||
gfx::ColorNone, gfx::ColorNone, 0, 0,
|
||||
&delegate);
|
||||
int lastPos = lastCaretPos();
|
||||
int i = MIN(m_scroll, lastPos);
|
||||
for (; i<lastPos; ++i) {
|
||||
int segmentWidth = 0;
|
||||
for (int j=m_scroll; j<i; ++j)
|
||||
segmentWidth += m_boxes[j].width;
|
||||
|
||||
int x = bounds().x + border().left() + delegate.bounds().w;
|
||||
int x = bounds().x + border().left() + segmentWidth;
|
||||
|
||||
if (mouseX > bounds().x2() - border().right()) {
|
||||
if (x >= bounds().x2() - border().right()) {
|
||||
@ -519,12 +510,12 @@ int Entry::getCaretFromMouse(MouseMessage* mousemsg)
|
||||
}
|
||||
}
|
||||
|
||||
return MID(0, i, textlen);
|
||||
return MID(0, i, lastPos);
|
||||
}
|
||||
|
||||
void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed)
|
||||
{
|
||||
std::wstring text = base::from_utf8(this->text());
|
||||
std::string text = this->text();
|
||||
int c, selbeg, selend;
|
||||
|
||||
getEntryThemeInfo(NULL, NULL, NULL, &selbeg, &selend);
|
||||
@ -537,7 +528,8 @@ void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed)
|
||||
case EntryCmd::InsertChar:
|
||||
// delete the entire selection
|
||||
if (selbeg >= 0) {
|
||||
text.erase(selbeg, selend-selbeg+1);
|
||||
text.erase(m_boxes[selbeg].from,
|
||||
m_boxes[selend].to - m_boxes[selbeg].from);
|
||||
|
||||
m_caret = selbeg;
|
||||
|
||||
@ -549,13 +541,20 @@ void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed)
|
||||
// we need to make m_scroll=0 to show the new inserted char.)
|
||||
// In this way, we first ensure a m_scroll value enough to
|
||||
// show the new inserted character.
|
||||
recalcCharBoxes(text);
|
||||
setCaretPos(m_caret);
|
||||
}
|
||||
|
||||
// put the character
|
||||
if (text.size() < m_maxsize) {
|
||||
ASSERT((std::size_t)m_caret <= text.size());
|
||||
text.insert(m_caret++, 1, unicodeChar);
|
||||
// Convert the unicode character -> wstring -> utf-8 string -> insert the utf-8 string
|
||||
if (lastCaretPos() < m_maxsize) {
|
||||
ASSERT((std::size_t)m_caret <= lastCaretPos());
|
||||
|
||||
std::wstring unicodeStr;
|
||||
unicodeStr.push_back(unicodeChar);
|
||||
|
||||
text.insert(m_boxes[m_caret].from,
|
||||
base::to_utf8(unicodeStr));
|
||||
++m_caret;
|
||||
}
|
||||
|
||||
m_select = -1;
|
||||
@ -630,20 +629,20 @@ void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed)
|
||||
// delete the entire selection
|
||||
if (selbeg >= 0) {
|
||||
// *cut* text!
|
||||
if (cmd == EntryCmd::Cut) {
|
||||
std::wstring selected = text.substr(selbeg, selend - selbeg + 1);
|
||||
clip::set_text(base::to_utf8(selected));
|
||||
}
|
||||
if (cmd == EntryCmd::Cut)
|
||||
clip::set_text(selectedText());
|
||||
|
||||
// remove text
|
||||
text.erase(selbeg, selend-selbeg+1);
|
||||
text.erase(m_boxes[selbeg].from,
|
||||
m_boxes[selend].to - m_boxes[selbeg].from);
|
||||
|
||||
m_caret = selbeg;
|
||||
}
|
||||
// delete the next character
|
||||
else {
|
||||
if (m_caret < (int)text.size())
|
||||
text.erase(m_caret, 1);
|
||||
text.erase(m_boxes[m_caret].from,
|
||||
m_boxes[m_caret].to - m_boxes[m_caret].from);
|
||||
}
|
||||
|
||||
m_select = -1;
|
||||
@ -654,44 +653,53 @@ void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed)
|
||||
if (clip::get_text(clipboard)) {
|
||||
// delete the entire selection
|
||||
if (selbeg >= 0) {
|
||||
text.erase(selbeg, selend-selbeg+1);
|
||||
text.erase(m_boxes[selbeg].from,
|
||||
m_boxes[selend].to - m_boxes[selbeg].from);
|
||||
|
||||
m_caret = selbeg;
|
||||
m_select = -1;
|
||||
}
|
||||
|
||||
// paste text
|
||||
for (c=0; c<base::utf8_length(clipboard); c++) {
|
||||
if (text.size() < m_maxsize)
|
||||
text.insert(m_caret+c, 1,
|
||||
*(base::utf8_const_iterator(clipboard.begin())+c));
|
||||
else
|
||||
break;
|
||||
// Paste text
|
||||
recalcCharBoxes(text);
|
||||
int oldBoxes = m_boxes.size();
|
||||
|
||||
text.insert(m_boxes[m_caret].from, clipboard);
|
||||
|
||||
// Remove extra chars that do not fit in m_maxsize
|
||||
recalcCharBoxes(text);
|
||||
if (lastCaretPos() > m_maxsize) {
|
||||
text.erase(m_boxes[m_maxsize].from,
|
||||
text.size() - m_boxes[m_maxsize].from);
|
||||
recalcCharBoxes(text);
|
||||
}
|
||||
|
||||
setCaretPos(m_caret+c);
|
||||
int newBoxes = m_boxes.size();
|
||||
setCaretPos(m_caret+(newBoxes - oldBoxes));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EntryCmd::Copy:
|
||||
if (selbeg >= 0) {
|
||||
std::wstring selected = text.substr(selbeg, selend - selbeg + 1);
|
||||
clip::set_text(base::to_utf8(selected));
|
||||
}
|
||||
if (selbeg >= 0)
|
||||
clip::set_text(selectedText());
|
||||
break;
|
||||
|
||||
case EntryCmd::DeleteBackward:
|
||||
// delete the entire selection
|
||||
if (selbeg >= 0) {
|
||||
text.erase(selbeg, selend-selbeg+1);
|
||||
text.erase(m_boxes[selbeg].from,
|
||||
m_boxes[selend].to - m_boxes[selbeg].from);
|
||||
|
||||
m_caret = selbeg;
|
||||
}
|
||||
// delete the previous character
|
||||
else {
|
||||
if (m_caret > 0)
|
||||
text.erase(--m_caret, 1);
|
||||
if (m_caret > 0) {
|
||||
--m_caret;
|
||||
text.erase(m_boxes[m_caret].from,
|
||||
m_boxes[m_caret].to - m_boxes[m_caret].from);
|
||||
}
|
||||
}
|
||||
|
||||
m_select = -1;
|
||||
@ -700,13 +708,15 @@ void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed)
|
||||
case EntryCmd::DeleteBackwardWord:
|
||||
m_select = m_caret;
|
||||
backwardWord();
|
||||
if (m_caret < m_select)
|
||||
text.erase(m_caret, m_select-m_caret);
|
||||
if (m_caret < m_select) {
|
||||
text.erase(m_boxes[m_caret].from,
|
||||
m_boxes[m_select].to - m_boxes[m_caret].from);
|
||||
}
|
||||
m_select = -1;
|
||||
break;
|
||||
|
||||
case EntryCmd::DeleteForwardToEndOfLine:
|
||||
text.erase(m_caret);
|
||||
text.erase(m_boxes[m_caret].from);
|
||||
break;
|
||||
|
||||
case EntryCmd::SelectAll:
|
||||
@ -714,9 +724,8 @@ void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed)
|
||||
break;
|
||||
}
|
||||
|
||||
std::string newText = base::to_utf8(text);
|
||||
if (newText != this->text()) {
|
||||
setText(newText.c_str());
|
||||
if (text != this->text()) {
|
||||
setText(text);
|
||||
onChange();
|
||||
}
|
||||
|
||||
@ -730,18 +739,17 @@ void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed)
|
||||
|
||||
void Entry::forwardWord()
|
||||
{
|
||||
base::utf8_const_iterator utf8_begin = base::utf8_const_iterator(text().begin());
|
||||
int textlen = base::utf8_length(text());
|
||||
int textlen = lastCaretPos();
|
||||
int ch;
|
||||
|
||||
for (; m_caret < textlen; m_caret++) {
|
||||
ch = *(utf8_begin + m_caret);
|
||||
for (; m_caret < textlen; ++m_caret) {
|
||||
ch = m_boxes[m_caret].codepoint;
|
||||
if (IS_WORD_CHAR(ch))
|
||||
break;
|
||||
}
|
||||
|
||||
for (; m_caret < textlen; m_caret++) {
|
||||
ch = *(utf8_begin + m_caret);
|
||||
for (; m_caret < textlen; ++m_caret) {
|
||||
ch = m_boxes[m_caret].codepoint;
|
||||
if (!IS_WORD_CHAR(ch)) {
|
||||
++m_caret;
|
||||
break;
|
||||
@ -751,17 +759,16 @@ void Entry::forwardWord()
|
||||
|
||||
void Entry::backwardWord()
|
||||
{
|
||||
base::utf8_const_iterator utf8_begin = base::utf8_const_iterator(text().begin());
|
||||
int ch;
|
||||
|
||||
for (--m_caret; m_caret >= 0; --m_caret) {
|
||||
ch = *(utf8_begin + m_caret);
|
||||
ch = m_boxes[m_caret].codepoint;
|
||||
if (IS_WORD_CHAR(ch))
|
||||
break;
|
||||
}
|
||||
|
||||
for (; m_caret >= 0; --m_caret) {
|
||||
ch = *(utf8_begin + m_caret);
|
||||
ch = m_boxes[m_caret].codepoint;
|
||||
if (!IS_WORD_CHAR(ch)) {
|
||||
++m_caret;
|
||||
break;
|
||||
@ -774,7 +781,8 @@ void Entry::backwardWord()
|
||||
|
||||
bool Entry::isPosInSelection(int pos)
|
||||
{
|
||||
return (pos >= MIN(m_caret, m_select) && pos <= MAX(m_caret, m_select));
|
||||
return (pos >= MIN(m_caret, m_select) &&
|
||||
pos <= MAX(m_caret, m_select));
|
||||
}
|
||||
|
||||
void Entry::showEditPopupMenu(const gfx::Point& pt)
|
||||
@ -798,4 +806,55 @@ void Entry::showEditPopupMenu(const gfx::Point& pt)
|
||||
menu.showPopup(pt);
|
||||
}
|
||||
|
||||
class Entry::CalcBoxesTextDelegate : public she::DrawTextDelegate {
|
||||
public:
|
||||
CalcBoxesTextDelegate(const int end) : m_end(end) {
|
||||
}
|
||||
|
||||
const Entry::CharBoxes& boxes() const { return m_boxes; }
|
||||
|
||||
void preProcessChar(const int index,
|
||||
const int codepoint,
|
||||
gfx::Color& fg, gfx::Color& bg) override {
|
||||
if (!m_boxes.empty())
|
||||
m_boxes.back().to = index;
|
||||
|
||||
m_box = CharBox();
|
||||
m_box.codepoint = codepoint;
|
||||
m_box.from = index;
|
||||
m_box.to = m_end;
|
||||
}
|
||||
|
||||
bool preDrawChar(const gfx::Rect& charBounds) override {
|
||||
m_box.width = charBounds.w;
|
||||
return true;
|
||||
}
|
||||
|
||||
void postDrawChar(const gfx::Rect& charBounds) override {
|
||||
m_boxes.push_back(m_box);
|
||||
}
|
||||
|
||||
private:
|
||||
Entry::CharBox m_box;
|
||||
Entry::CharBoxes m_boxes;
|
||||
int m_end;
|
||||
};
|
||||
|
||||
void Entry::recalcCharBoxes(const std::string& text)
|
||||
{
|
||||
int lastTextIndex = int(text.size());
|
||||
CalcBoxesTextDelegate delegate(lastTextIndex);
|
||||
she::draw_text(nullptr, font(),
|
||||
base::utf8_const_iterator(text.begin()),
|
||||
base::utf8_const_iterator(text.end()),
|
||||
gfx::ColorNone, gfx::ColorNone, 0, 0, &delegate);
|
||||
m_boxes = delegate.boxes();
|
||||
|
||||
// A last box for the last position
|
||||
CharBox box;
|
||||
box.codepoint = 0;
|
||||
box.from = box.to = lastTextIndex;
|
||||
m_boxes.push_back(box);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
@ -23,18 +23,21 @@ namespace ui {
|
||||
|
||||
void setMaxTextLength(const std::size_t maxsize);
|
||||
|
||||
bool isPassword() const;
|
||||
bool isReadOnly() const;
|
||||
void setReadOnly(bool state);
|
||||
void setPassword(bool state);
|
||||
|
||||
void showCaret();
|
||||
void hideCaret();
|
||||
|
||||
int caretPos() const { return m_caret; }
|
||||
int lastCaretPos() const;
|
||||
|
||||
void setCaretPos(int pos);
|
||||
void setCaretToEnd();
|
||||
void selectText(int from, int to);
|
||||
void selectAllText();
|
||||
void deselectText();
|
||||
std::string selectedText() const;
|
||||
|
||||
void setSuffix(const std::string& suffix);
|
||||
const std::string& getSuffix() { return m_suffix; }
|
||||
@ -43,7 +46,7 @@ namespace ui {
|
||||
|
||||
// for themes
|
||||
void getEntryThemeInfo(int* scroll, int* caret, int* state,
|
||||
int* selbeg, int* selend);
|
||||
int* selbeg, int* selend) const;
|
||||
gfx::Rect getEntryTextBounds() const;
|
||||
|
||||
// Signals
|
||||
@ -86,7 +89,20 @@ namespace ui {
|
||||
void backwardWord();
|
||||
bool isPosInSelection(int pos);
|
||||
void showEditPopupMenu(const gfx::Point& pt);
|
||||
void recalcCharBoxes(const std::string& text);
|
||||
|
||||
class CalcBoxesTextDelegate;
|
||||
|
||||
struct CharBox {
|
||||
int codepoint;
|
||||
int from, to;
|
||||
int width;
|
||||
CharBox() { codepoint = from = to = width = 0; }
|
||||
};
|
||||
|
||||
typedef std::vector<CharBox> CharBoxes;
|
||||
|
||||
CharBoxes m_boxes;
|
||||
Timer m_timer;
|
||||
std::size_t m_maxsize;
|
||||
int m_caret;
|
||||
@ -95,7 +111,6 @@ namespace ui {
|
||||
bool m_hidden;
|
||||
bool m_state; // show or not the text caret
|
||||
bool m_readonly;
|
||||
bool m_password;
|
||||
bool m_recent_focused;
|
||||
bool m_lock_selection;
|
||||
bool m_translate_dead_keys;
|
||||
|
@ -246,17 +246,14 @@ public:
|
||||
|
||||
gfx::Rect bounds() const { return m_bounds; }
|
||||
|
||||
void preProcessChar(const base::utf8_const_iterator& it,
|
||||
const base::utf8_const_iterator& end,
|
||||
int& chr,
|
||||
void preProcessChar(const int index,
|
||||
const int codepoint,
|
||||
gfx::Color& fg,
|
||||
gfx::Color& bg,
|
||||
bool& drawChar,
|
||||
bool& moveCaret) override {
|
||||
if (!m_surface)
|
||||
drawChar = false;
|
||||
else {
|
||||
if (m_mnemonic && std::tolower(chr) == m_mnemonic) {
|
||||
gfx::Color& bg) override {
|
||||
if (m_surface) {
|
||||
if (m_mnemonic &&
|
||||
// TODO use ICU library to lower unicode chars
|
||||
std::tolower(codepoint) == m_mnemonic) {
|
||||
m_underscoreColor = fg;
|
||||
m_mnemonic = 0; // Just one time
|
||||
}
|
||||
|
@ -125,11 +125,6 @@ double Widget::textDouble() const
|
||||
return strtod(m_text.c_str(), NULL);
|
||||
}
|
||||
|
||||
int Widget::textLength() const
|
||||
{
|
||||
return base::utf8_length(text());
|
||||
}
|
||||
|
||||
void Widget::setText(const std::string& text)
|
||||
{
|
||||
setTextQuiet(text);
|
||||
|
@ -84,7 +84,6 @@ namespace ui {
|
||||
const std::string& text() const { return m_text; }
|
||||
int textInt() const;
|
||||
double textDouble() const;
|
||||
int textLength() const;
|
||||
void setText(const std::string& text);
|
||||
void setTextf(const char* text, ...);
|
||||
void setTextQuiet(const std::string& text);
|
||||
|
3
third_party/CMakeLists.txt
vendored
3
third_party/CMakeLists.txt
vendored
@ -1,5 +1,5 @@
|
||||
# ASEPRITE
|
||||
# Copyright (C) 2001-2016 David Capello
|
||||
# Copyright (C) 2001-2017 David Capello
|
||||
|
||||
include_directories(.)
|
||||
|
||||
@ -85,6 +85,7 @@ if(NOT USE_SHARED_FREETYPE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(harfbuzz-cmake)
|
||||
add_subdirectory(simpleini)
|
||||
|
||||
# Add cmark without tests
|
||||
|
1
third_party/harfbuzz
vendored
Submodule
1
third_party/harfbuzz
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 8a4c80563dbc74608e5c65df395a13ee4840cbf3
|
Loading…
x
Reference in New Issue
Block a user