Commit 047b36f9 authored by krb's avatar krb Committed by Commit bot

Also, updated google.patch, README.chromium, BUILD.gn. Removed unused files.

BUG=643804

Review-Url: https://codereview.chromium.org/2544793003
Cr-Commit-Position: refs/heads/master@{#438176}
parent 3248025c
......@@ -86,7 +86,7 @@ bool HunspellEngine::CheckSpelling(const base::string16& word_to_check,
// to check rather than crash.
if (hunspell_.get()) {
// |hunspell_->spell| returns 0 if the word is misspelled.
word_correct = (hunspell_->spell(word_to_check_utf8.c_str()) != 0);
word_correct = (hunspell_->spell(word_to_check_utf8) != 0);
}
}
......@@ -106,18 +106,14 @@ void HunspellEngine::FillSuggestionList(
if (!hunspell_.get())
return;
char** suggestions = NULL;
int number_of_suggestions =
hunspell_->suggest(&suggestions, wrong_word_utf8.c_str());
std::vector<std::string> suggestions =
hunspell_->suggest(wrong_word_utf8);
// Populate the vector of WideStrings.
for (int i = 0; i < number_of_suggestions; ++i) {
for (size_t i = 0; i < suggestions.size(); ++i) {
if (i < spellcheck::kMaxSuggestions)
optional_suggestions->push_back(base::UTF8ToUTF16(suggestions[i]));
free(suggestions[i]);
}
if (suggestions != NULL)
free(suggestions);
}
bool HunspellEngine::InitializeIfNeeded() {
......
......@@ -1333,7 +1333,8 @@ TEST_F(SpellCheckTest, NoSuggest) {
EXPECT_EQ(kTestCases[i].should_pass, result) << kTestCases[i].suggestion <<
" in " << kTestCases[i].locale;
// TODO(cb/673424): Bring this back when suggestions are sped up.
#if 0
// Now verify that this test case does not show up as a suggestion.
std::vector<base::string16> suggestions;
size_t input_length = 0;
......@@ -1360,6 +1361,7 @@ TEST_F(SpellCheckTest, NoSuggest) {
" in " << kTestCases[i].locale;
}
}
#endif
}
}
......
......@@ -34,8 +34,6 @@ static_library("hunspell") {
"src/hunspell/baseaffix.hxx",
"src/hunspell/csutil.cxx",
"src/hunspell/csutil.hxx",
"src/hunspell/dictmgr.cxx",
"src/hunspell/dictmgr.hxx",
"src/hunspell/filemgr.cxx",
"src/hunspell/filemgr.hxx",
"src/hunspell/hashmgr.cxx",
......@@ -53,7 +51,6 @@ static_library("hunspell") {
"src/hunspell/replist.hxx",
"src/hunspell/suggestmgr.cxx",
"src/hunspell/suggestmgr.hxx",
"src/hunspell/utf_info.hxx",
"src/hunspell/w_char.hxx",
"src/parsers/textparser.cxx",
"src/parsers/textparser.hxx",
......
......@@ -30,12 +30,14 @@ Main features of Hunspell spell checker and morphological analyzer:
- Free software (LGPL, GPL, MPL tri-license)
Compiling on Unix/Linux
-----------------------
Compiling on Unix/Linux and others
----------------------------------
autoreconf -vfi
./configure
make
make install
make install #if neccesary prefix with sudo
ldconfig #not needed on windows, on linux sudo may be needed
For dictionary development, use the --with-warnings option of configure.
......@@ -43,59 +45,48 @@ For interactive user interface of Hunspell executable, use the --with-ui option.
The developer packages you need to compile Hunspell's interface:
glibc-devel
autoconf automake autopoint libtool g++
optional developer packages:
ncurses (need for --with-ui)
ncurses (need for --with-ui), eg. libncursesw5 for UTF-8
readline (for fancy input line editing,
configure parameter: --with-readline)
locale and gettext (but you can also use the
--with-included-gettext configure parameter)
Hunspell distribution uses new Autoconf (2.59) and Automake (1.9).
Compiling on Windows
--------------------
1. Compiling with Windows SDK
1. Compiling with Visual Studio
Download the free Visual Studio Community Edition of Microsoft, open the
file hunspell/src/win_api/Hunspell.sln. Select the appropirate build (Debug,
Release, Win32, x64) and press Build.
2. Compiling with Mingw64 and MSYS2
Download Msys2, update everything and install the following packages:
Download the free Windows SDK of Microsoft, open a command prompt
window and cd into hunspell/src/win_api. Use the following command
to compile hunspell:
pacman -S base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-libtool
vcbuild
Open Mingw-w64 Win64 prompt and compile the same way as on Linux, see above.
2. Compiling in Cygwin environment
3. Compiling in Cygwin environment
Download and install Cygwin environment for Windows with the following
extra packages:
make
automake
autoconf
gcc-g++ development package
mingw development package (for cygwin.dll free native Windows compilation)
ncurses, readline (for user interface)
iconv (character conversion)
2.1. Cygwin1.dll dependent compiling
Open a Cygwin shell, cd into the hunspell root directory:
./configure
make
make install
For dictionary development, use the --with-warnings option of configure.
For interactive user interface of Hunspell executable, use the --with-ui option.
readline configure parameter: --with-readline (for fancy input line editing)
1.2. Cygwin1.dll free compiling
Open a Cygwin shell, cd into the hunspell/src/win_api and
3.1. Cygwin1.dll dependent compiling
make -f Makefile.cygwin
Same as on Linux.
Testing
-------
......@@ -118,11 +109,11 @@ Documentation
-------------
features and dictionary format:
man 4 hunspell
man 5 hunspell
man hunspell
hunspell -h
http://hunspell.sourceforge.net
http://hunspell.github.io/
Usage
-----
......@@ -169,11 +160,14 @@ Dictionaries
------------
Myspell & Hunspell dictionaries:
http://extensions.libreoffice.org
http://cgit.freedesktop.org/libreoffice/dictionaries
http://extensions.openoffice.org
http://wiki.services.openoffice.org/wiki/Dictionaries
Aspell dictionaries (need some conversion):
ftp://ftp.gnu.org/gnu/aspell/dict
Conversion steps: see relevant feature request at http://hunspell.sf.net.
Conversion steps: see relevant feature request at http://hunspell.github.io/ .
László Németh
nemeth at OOo
nemeth at numbertext org
Name: hunspell
URL: http://hunspell.sourceforge.net/
Version: 1.3.2
Version: 1.5.4
License: MPL 1.1/GPL 2.0/LGPL 2.1
License File: COPYING
Security Critical: yes
Description:
This is a partial copy of Hunspell 1.3.2 with the following changes:
This is a partial copy of Hunspell 1.5.4 with the following changes:
* Remove '#include "config.h"' from src/hunspell/hunspell.hxx
* Remove '#include "config.h"' from src/hunspell/license.hunspell
* Change src/hunspell/filemgr.hxx and src/hunspell/filemgr.cxx to use
LineIterator.
* Add ScopedHashEntry, which creates temporary hentry objects, to
......@@ -26,15 +25,14 @@ The patch is in google.patch.
Chromium-specific changes are in google.patch. To update the patch, follow these
steps, or simply run update_google_patch.sh from the commandline.
1) Checkout hunspell:
$ cvs -z3 \
-d:pserver:anonymous@hunspell.cvs.sourceforge.net:/cvsroot/hunspell \
co -D "23 Mar 2012" -P hunspell
2) Apply the existing patch:
$ git clone https://github.com/hunspell/hunspell.git
$ cd hunspell
$ git checkout v1.5.4
2) Apply the existing patch:
$ patch -p0 -i ~/src/third_party/hunspell/google.patch
3) Make your new changes inside the CVS hunspell directory.
4) Generate the updated patch:
$ cvs diff -u > ~/src/third_party/hunspell/google.patch
$ git diff > ~/src/third_party/hunspell/google.patch
All dictionaries used by Chromium has been checked in to the
'third_party/hunspell_dictionaries' directory. They have several additions over
......
......@@ -26,11 +26,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
base::string16 utf16_string = base::UTF8ToUTF16(data_string);
data_string = base::UTF16ToUTF8(utf16_string);
hunspell->spell(data_string.c_str());
hunspell->spell(data_string);
char** suggestions = nullptr;
int suggestion_length = hunspell->suggest(&suggestions, data_string.c_str());
hunspell->free_list(&suggestions, suggestion_length);
std::vector<std::string> suggestions =
hunspell->suggest(data_string);
return 0;
}
This diff is collapsed.
lib_LTLIBRARIES = libhunspell-1.3.la
libhunspell_1_3_includedir = $(includedir)/hunspell
libhunspell_1_3_la_SOURCES=affentry.cxx affixmgr.cxx csutil.cxx \
dictmgr.cxx hashmgr.cxx hunspell.cxx \
suggestmgr.cxx license.myspell license.hunspell \
phonet.cxx filemgr.cxx hunzip.cxx replist.cxx
libhunspell_1_3_include_HEADERS=affentry.hxx htypes.hxx affixmgr.hxx \
csutil.hxx hunspell.hxx atypes.hxx dictmgr.hxx hunspell.h \
suggestmgr.hxx baseaffix.hxx hashmgr.hxx langnum.hxx \
phonet.hxx filemgr.hxx hunzip.hxx w_char.hxx replist.hxx \
hunvisapi.h
libhunspell_1_3_la_DEPENDENCIES=utf_info.cxx
libhunspell_1_3_la_LDFLAGS=-no-undefined
AM_CXXFLAGS=$(CFLAG_VISIBILITY) -DBUILDING_LIBHUNSPELL
EXTRA_DIST=hunspell.dsp makefile.mk README utf_info.cxx
This diff is collapsed.
Hunspell spell checker and morphological analyser library
Documentation, tests, examples: http://hunspell.sourceforge.net
Documentation, tests, examples: http://hunspell.github.io/
Author of Hunspell:
László Németh (nemethl (at) gyorsposta.hu)
......
#ifndef _ATYPES_HXX_
#define _ATYPES_HXX_
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Hunspell, based on MySpell.
*
* The Initial Developers of the Original Code are
* Kevin Hendricks (MySpell) and Németh László (Hunspell).
* Portions created by the Initial Developers are Copyright (C) 2002-2005
* the Initial Developers. All Rights Reserved.
*
* Contributor(s): David Einstein, Davide Prina, Giuseppe Modugno,
* Gianluca Turconi, Simon Brouwer, Noll János, Bíró Árpád,
* Goldman Eleonóra, Sarlós Tamás, Bencsáth Boldizsár, Halácsy Péter,
* Dvornik László, Gefferth András, Nagy Viktor, Varga Dániel, Chris Halls,
* Rene Engelhard, Bram Moolenaar, Dafydd Jones, Harri Pitkänen
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef ATYPES_HXX_
#define ATYPES_HXX_
#ifndef HUNSPELL_WARNING
#include <stdio.h>
#ifdef HUNSPELL_WARNING_ON
#define HUNSPELL_WARNING fprintf
#else
// empty inline function to switch off warnings (instead of the C99 standard variadic macros)
static inline void HUNSPELL_WARNING(FILE *, const char *, ...) {}
// empty inline function to switch off warnings (instead of the C99 standard
// variadic macros)
static inline void HUNSPELL_WARNING(FILE*, const char*, ...) {}
#endif
#endif
// HUNSTEM def.
#define HUNSTEM
#include "hashmgr.hxx"
#include "w_char.hxx"
#include <algorithm>
#include <string>
#include <vector>
#define SETSIZE 256
#define CONTSIZE 65536
#define MAXWORDLEN 100
#define MAXWORDUTF8LEN 256
#define SETSIZE 256
#define CONTSIZE 65536
// affentry options
#define aeXPRODUCT (1 << 0)
#define aeUTF8 (1 << 1)
#define aeALIASF (1 << 2)
#define aeALIASM (1 << 3)
#define aeLONGCOND (1 << 4)
// AffEntry options
#define aeXPRODUCT (1 << 0)
#define aeUTF8 (1 << 1)
#define aeALIASF (1 << 2)
#define aeALIASM (1 << 3)
#define aeLONGCOND (1 << 4)
// compound options
#define IN_CPD_NOT 0
#define IN_CPD_NOT 0
#define IN_CPD_BEGIN 1
#define IN_CPD_END 2
#define IN_CPD_END 2
#define IN_CPD_OTHER 3
// info options
#define SPELL_COMPOUND (1 << 0)
#define SPELL_FORBIDDEN (1 << 1)
#define SPELL_ALLCAP (1 << 2)
#define SPELL_NOCAP (1 << 3)
#define SPELL_INITCAP (1 << 4)
#define SPELL_ORIGCAP (1 << 5)
#define SPELL_WARN (1 << 6)
#define SPELL_COMPOUND (1 << 0)
#define SPELL_FORBIDDEN (1 << 1)
#define SPELL_ALLCAP (1 << 2)
#define SPELL_NOCAP (1 << 3)
#define SPELL_INITCAP (1 << 4)
#define SPELL_ORIGCAP (1 << 5)
#define SPELL_WARN (1 << 6)
#define MAXLNLEN 8192
#define MINCPDLEN 3
#define MAXCOMPOUND 10
#define MAXCONDLEN 20
#define MAXCONDLEN_1 (MAXCONDLEN - sizeof(char*))
#define MINCPDLEN 3
#define MAXCOMPOUND 10
#define MAXCONDLEN 20
#define MAXCONDLEN_1 (MAXCONDLEN - sizeof(char *))
#define MAXACC 1000
#define MAXACC 1000
#define FLAG unsigned short
#define FLAG_NULL 0x00
#define FREE_FLAG(a) a = 0
#define TESTAFF( a, b , c ) (flag_bsearch((unsigned short *) a, (unsigned short) b, c))
struct affentry
{
char * strip;
char * appnd;
unsigned char stripl;
unsigned char appndl;
char numconds;
char opts;
unsigned short aflag;
unsigned short * contclass;
short contclasslen;
union {
char conds[MAXCONDLEN];
struct {
char conds1[MAXCONDLEN_1];
char * conds2;
} l;
} c;
char * morphcode;
};
#define TESTAFF(a, b, c) (std::binary_search(a, a + c, b))
struct guessword {
char * word;
char* word;
bool allow;
char * orig;
char* orig;
};
struct mapentry {
char ** set;
int len;
};
struct flagentry {
FLAG * def;
int len;
};
typedef std::vector<std::string> mapentry;
typedef std::vector<FLAG> flagentry;
struct patentry {
char * pattern;
char * pattern2;
char * pattern3;
std::string pattern;
std::string pattern2;
std::string pattern3;
FLAG cond;
FLAG cond2;
patentry()
: cond(FLAG_NULL)
, cond2(FLAG_NULL) {
}
};
#endif
#ifndef _BASEAFF_HXX_
#define _BASEAFF_HXX_
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Hunspell, based on MySpell.
*
* The Initial Developers of the Original Code are
* Kevin Hendricks (MySpell) and Németh László (Hunspell).
* Portions created by the Initial Developers are Copyright (C) 2002-2005
* the Initial Developers. All Rights Reserved.
*
* Contributor(s): David Einstein, Davide Prina, Giuseppe Modugno,
* Gianluca Turconi, Simon Brouwer, Noll János, Bíró Árpád,
* Goldman Eleonóra, Sarlós Tamás, Bencsáth Boldizsár, Halácsy Péter,
* Dvornik László, Gefferth András, Nagy Viktor, Varga Dániel, Chris Halls,
* Rene Engelhard, Bram Moolenaar, Dafydd Jones, Harri Pitkänen
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "hunvisapi.h"
#ifndef BASEAFF_HXX_
#define BASEAFF_HXX_
class LIBHUNSPELL_DLL_EXPORTED AffEntry
{
protected:
char * appnd;
char * strip;
unsigned char appndl;
unsigned char stripl;
char numconds;
char opts;
unsigned short aflag;
union {
char conds[MAXCONDLEN];
struct {
char conds1[MAXCONDLEN_1];
char * conds2;
} l;
} c;
char * morphcode;
unsigned short * contclass;
short contclasslen;
#include <string>
class AffEntry {
private:
AffEntry(const AffEntry&);
AffEntry& operator=(const AffEntry&);
public:
AffEntry()
: numconds(0),
opts(0),
aflag(0),
morphcode(0),
contclass(NULL),
contclasslen(0) {}
virtual ~AffEntry();
std::string appnd;
std::string strip;
unsigned char numconds;
char opts;
unsigned short aflag;
union {
char conds[MAXCONDLEN];
struct {
char conds1[MAXCONDLEN_1];
char* conds2;
} l;
} c;
char* morphcode;
unsigned short* contclass;
short contclasslen;
};
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include "dictmgr.hxx"
DictMgr::DictMgr(const char * dictpath, const char * etype) : numdict(0)
{
// load list of etype entries
pdentry = (dictentry *)malloc(MAXDICTIONARIES*sizeof(struct dictentry));
if (pdentry) {
if (parse_file(dictpath, etype)) {
numdict = 0;
// no dictionary.lst found is okay
}
}
}
DictMgr::~DictMgr()
{
dictentry * pdict = NULL;
if (pdentry) {
pdict = pdentry;
for (int i=0;i<numdict;i++) {
if (pdict->lang) {
free(pdict->lang);
pdict->lang = NULL;
}
if (pdict->region) {
free(pdict->region);
pdict->region=NULL;
}
if (pdict->filename) {
free(pdict->filename);
pdict->filename = NULL;
}
pdict++;
}
free(pdentry);
pdentry = NULL;
pdict = NULL;
}
numdict = 0;
}
// read in list of etype entries and build up structure to describe them
int DictMgr::parse_file(const char * dictpath, const char * etype)
{
int i;
char line[MAXDICTENTRYLEN+1];
dictentry * pdict = pdentry;
// open the dictionary list file
FILE * dictlst;
dictlst = fopen(dictpath,"r");
if (!dictlst) {
return 1;
}
// step one is to parse the dictionary list building up the
// descriptive structures
// read in each line ignoring any that dont start with etype
while (fgets(line,MAXDICTENTRYLEN,dictlst)) {
mychomp(line);
/* parse in a dictionary entry */
if (strncmp(line,etype,4) == 0) {
if (numdict < MAXDICTIONARIES) {
char * tp = line;
char * piece;
i = 0;
while ((piece=mystrsep(&tp,' '))) {
if (*piece != '\0') {
switch(i) {
case 0: break;
case 1: pdict->lang = mystrdup(piece); break;
case 2: if (strcmp (piece, "ANY") == 0)
pdict->region = mystrdup("");
else
pdict->region = mystrdup(piece);
break;
case 3: pdict->filename = mystrdup(piece); break;
default: break;
}
i++;
}
free(piece);
}
if (i == 4) {
numdict++;
pdict++;
} else {
switch (i) {
case 3:
free(pdict->region);
pdict->region=NULL;
case 2: //deliberate fallthrough
free(pdict->lang);
pdict->lang=NULL;
default:
break;
}
fprintf(stderr,"dictionary list corruption in line \"%s\"\n",line);
fflush(stderr);
}
}
}
}
fclose(dictlst);
return 0;
}
// return text encoding of dictionary
int DictMgr::get_list(dictentry ** ppentry)
{
*ppentry = pdentry;
return numdict;
}
// strip strings into token based on single char delimiter
// acts like strsep() but only uses a delim char and not
// a delim string
char * DictMgr::mystrsep(char ** stringp, const char delim)
{
char * rv = NULL;
char * mp = *stringp;
size_t n = strlen(mp);
if (n > 0) {
char * dp = (char *)memchr(mp,(int)((unsigned char)delim),n);
if (dp) {
*stringp = dp+1;
size_t nc = dp - mp;
rv = (char *) malloc(nc+1);
if (rv) {
memcpy(rv,mp,nc);
*(rv+nc) = '\0';
}
} else {
rv = (char *) malloc(n+1);
if (rv) {
memcpy(rv, mp, n);
*(rv+n) = '\0';
*stringp = mp + n;
}
}
}
return rv;
}
// replaces strdup with ansi version
char * DictMgr::mystrdup(const char * s)
{
char * d = NULL;
if (s) {
int sl = strlen(s)+1;
d = (char *) malloc(sl);
if (d) memcpy(d,s,sl);
}
return d;
}
// remove cross-platform text line end characters
void DictMgr:: mychomp(char * s)
{
int k = strlen(s);
if ((k > 0) && ((*(s+k-1)=='\r') || (*(s+k-1)=='\n'))) *(s+k-1) = '\0';
if ((k > 1) && (*(s+k-2) == '\r')) *(s+k-2) = '\0';
}
#ifndef _DICTMGR_HXX_
#define _DICTMGR_HXX_
#include "hunvisapi.h"
#define MAXDICTIONARIES 100
#define MAXDICTENTRYLEN 1024
struct dictentry {
char * filename;
char * lang;
char * region;
};
class LIBHUNSPELL_DLL_EXPORTED DictMgr
{
int numdict;
dictentry * pdentry;
public:
DictMgr(const char * dictpath, const char * etype);
~DictMgr();
int get_list(dictentry** ppentry);
private:
int parse_file(const char * dictpath, const char * etype);
char * mystrsep(char ** stringp, const char delim);
char * mystrdup(const char * s);
void mychomp(char * s);
};
#endif
This diff is collapsed.
#ifndef _HUNSPELL_VISIBILITY_H_
#define _HUNSPELL_VISIBILITY_H_
#ifndef HUNSPELL_VISIBILITY_H_
#define HUNSPELL_VISIBILITY_H_
#if defined(HUNSPELL_STATIC)
# define LIBHUNSPELL_DLL_EXPORTED
......@@ -9,7 +9,7 @@
# else
# define LIBHUNSPELL_DLL_EXPORTED __declspec(dllimport)
# endif
#elif BUILDING_LIBHUNSPELL && 1
#elif defined(BUILDING_LIBHUNSPELL) && 1
# define LIBHUNSPELL_DLL_EXPORTED __attribute__((__visibility__("default")))
#else
# define LIBHUNSPELL_DLL_EXPORTED
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment