Merge branch 'staged' into dev
Some checks are pending
Release All / build_macOS (macos-13, 6.6.3) (push) Waiting to run
Release All / build_macOS (macos-13, 6.7.2) (push) Waiting to run
Release All / build_macOS (macos-14, 6.6.3) (push) Waiting to run
Release All / build_macOS (macos-14, 6.7.2) (push) Waiting to run
Release All / build_Windows (windows-2022, 6.6.3) (push) Waiting to run
Release All / build_Windows (windows-2022, 6.7.2) (push) Waiting to run
Release All / generate_other_staffs (push) Waiting to run
Release All / publish (push) Blocked by required conditions

This commit is contained in:
YiFang Xiao 2024-12-17 20:32:37 +08:00
commit 7f62fc87c4
18 changed files with 204 additions and 81 deletions

View file

@ -56,7 +56,7 @@ jobs:
brew update
- name: Install dependencies
run: |
brew install \
brew install --force --overwrite \
ninja \
opencc \
ffmpeg \
@ -68,7 +68,7 @@ jobs:
hunspell \
xapian \
libzim \
qt
qt || true
- name: Install eb
run: |
wget https://github.com/mistydemeo/eb/releases/download/v4.4.3/eb-4.4.3.tar.bz2

View file

@ -31,7 +31,7 @@ jobs:
brew update
- name: Install dependencies
run: |
brew install \
brew install --force --overwrite \
bzip2 \
create-dmg \
hunspell \
@ -42,7 +42,7 @@ jobs:
lzip \
ninja \
opencc \
xapian
xapian || true
- name: Install eb
run: |
git clone https://github.com/xiaoyifang/eb.git

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View file

@ -0,0 +1,32 @@
#include "dictionary_icon_name.hh"
#include <QMutexLocker>
QString Icons::DictionaryIconName::getIconName( const QString & dictionaryName )
{
if ( dictionaryName.isEmpty() ) {
return {};
}
QMutexLocker _( &_mutex );
auto it = _dictionaryIconNames.contains( dictionaryName );
if ( it ) {
return _dictionaryIconNames.value( dictionaryName );
}
//get the first character of the dictionary name
QString name = dictionaryName.at( 0 ).toUpper();
auto it1 = _iconDictionaryNames.contains( name );
std::vector< QString > vector = {};
if ( it1 ) {
vector = _iconDictionaryNames.value( name );
vector.emplace_back( dictionaryName );
}
else {
vector.emplace_back( dictionaryName );
_iconDictionaryNames.insert( name, vector );
}
name = name + QString::number( vector.size() );
_dictionaryIconNames.insert( dictionaryName, name );
return name;
}

View file

@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <QMap>
#include <vector>
#include <mutex>
#include <QString>
#include <QMutex>
namespace Icons {
//use dictionary name's (first character + the order number) to represent the dictionary name in the icon image.
class DictionaryIconName
{
//map icon name to dictionary names;
QMap< QString, std::vector< QString > > _iconDictionaryNames;
//map dictionary name to icon name;
QMap< QString, QString > _dictionaryIconNames;
QMutex _mutex;
public:
QString getIconName( const QString & dictionaryName );
};
} // namespace Icons

View file

@ -155,8 +155,7 @@ std::u32string applyWhitespaceAndPunctOnly( std::u32string const & in )
bool isWhitespace( char32_t ch )
{
//invisible character should be treated as whitespace as well.
return QChar::isSpace( ch ) || !QChar::isPrint( ch );
return QChar::isSpace( ch );
}
bool isWhitespaceOrPunct( char32_t ch )

View file

@ -38,4 +38,22 @@ bool GlobalBroadcaster::existedInWhitelist( QString url ) const
{
return whitelist.contains( url );
}
QString GlobalBroadcaster::getAbbrName( QString const & text )
{
if ( text.isEmpty() ) {
return {};
}
//remove whitespace,number,mark,puncuation,symbol
QString simplified = text;
simplified.remove(
QRegularExpression( R"([\p{Z}\p{N}\p{M}\p{P}\p{S}])", QRegularExpression::UseUnicodePropertiesOption ) );
if ( simplified.isEmpty() ) {
return {};
}
return _icon_names.getIconName( simplified );
}
// namespace global

View file

@ -5,6 +5,7 @@
#include "config.hh"
#include "pronounceengine.hh"
#include <QCache>
#include "dictionary_icon_name.hh"
struct ActiveDictIds
{
@ -25,6 +26,7 @@ class GlobalBroadcaster: public QObject
Config::Preferences * preference;
QSet< QString > whitelist;
Icons::DictionaryIconName _icon_names;
public:
void setPreference( Config::Preferences * _pre );
@ -40,6 +42,7 @@ public:
QMap< QString, QSet< QString > > folderFavoritesMap;
QMap< unsigned, QString > groupFolderMap;
PronounceEngine pronounce_engine;
QString getAbbrName( QString const & text );
signals:
void dictionaryChanges( ActiveDictIds ad );
void dictionaryClear( ActiveDictIds ad );

View file

@ -71,8 +71,8 @@ const static QRegularExpression accentMark( R"(\p{M})", QRegularExpression::UseU
//contain unicode space mark,invisible, and punctuation
const static QRegularExpression markPuncSpace( R"([\p{M}\p{Z}\p{C}\p{P}])",
QRegularExpression::UseUnicodePropertiesOption );
//contain unicode space and mark.invisible
const static QRegularExpression markSpace( R"([\p{M}\p{Z}\p{C}])", QRegularExpression::UseUnicodePropertiesOption );
//contain unicode space and mark.
const static QRegularExpression markSpace( R"([\p{M}\p{Z}])", QRegularExpression::UseUnicodePropertiesOption );
const static QRegularExpression whiteSpace( "\\s+" );

View file

@ -39,6 +39,19 @@ inline QString rstrip( const QString & str )
return {};
}
inline uint32_t leadingSpaceCount( const QString & str )
{
for ( int i = 0; i < str.size(); i++ ) {
if ( str.at( i ).isSpace() ) {
continue;
}
else {
return i;
}
}
return 0;
}
std::string c_string( const QString & str );
bool endsWithIgnoreCase( QByteArrayView str, QByteArrayView extension );
/**

View file

@ -19,6 +19,7 @@
#include <QRegularExpression>
#include "utils.hh"
#include "zipfile.hh"
#include <array>
namespace Dictionary {
@ -291,7 +292,7 @@ bool Class::loadIconFromFile( QString const & _filename, bool isFullName )
return false;
}
bool Class::loadIconFromText( QString iconUrl, QString const & text )
bool Class::loadIconFromText( const QString & iconUrl, QString const & text )
{
if ( text.isEmpty() ) {
return false;
@ -308,7 +309,7 @@ bool Class::loadIconFromText( QString iconUrl, QString const & text )
painter.setCompositionMode( QPainter::CompositionMode_SourceAtop );
QFont font = painter.font();
//the text should be a little smaller than the icon
//the orderNum should be a little smaller than the icon
font.setPixelSize( iconSize * 0.6 );
font.setWeight( QFont::Bold );
painter.setFont( font );
@ -318,8 +319,21 @@ bool Class::loadIconFromText( QString iconUrl, QString const & text )
//select a single char.
auto abbrName = getAbbrName( text );
painter.setPen( QColor( 4, 57, 108, 200 ) );
painter.drawText( rectangle, Qt::AlignCenter, abbrName );
painter.setPen( intToFixedColor( qHash( abbrName ) ) );
// Draw first character
painter.drawText( rectangle, Qt::AlignCenter, abbrName.at( 0 ) );
//the orderNum should be a little smaller than the icon
font.setPixelSize( iconSize * 0.4 );
QFontMetrics fm1( font );
const QString & orderNum = abbrName.mid( 1 );
int orderNumberWidth = fm1.horizontalAdvance( orderNum );
painter.setFont( font );
painter.drawText( rectangle.x() + rectangle.width() - orderNumberWidth * 1.2,
rectangle.y() + rectangle.height(),
orderNum );
painter.end();
@ -330,35 +344,30 @@ bool Class::loadIconFromText( QString iconUrl, QString const & text )
return false;
}
QColor Class::intToFixedColor( int index )
{
// Predefined list of colors
static const std::array colors = {
QColor( 255, 0, 0, 200 ), // Red
QColor( 4, 57, 108, 200 ), //Custom
QColor( 0, 255, 0, 200 ), // Green
QColor( 0, 0, 255, 200 ), // Blue
QColor( 255, 255, 0, 200 ), // Yellow
QColor( 0, 255, 255, 200 ), // Cyan
QColor( 255, 0, 255, 200 ), // Magenta
QColor( 192, 192, 192, 200 ), // Gray
QColor( 255, 165, 0, 200 ), // Orange
QColor( 128, 0, 128, 200 ), // Violet
QColor( 128, 128, 0, 200 ) // Olive
};
// Use modulo operation to ensure index is within the range of the color list
return colors[ index % colors.size() ];
}
QString Class::getAbbrName( QString const & text )
{
if ( text.isEmpty() ) {
return {};
}
//remove whitespace,number,mark,puncuation,symbol
QString simplified = text;
simplified.remove(
QRegularExpression( R"([\p{Z}\p{N}\p{M}\p{P}\p{S}])", QRegularExpression::UseUnicodePropertiesOption ) );
if ( simplified.isEmpty() ) {
return {};
}
int index = qHash( simplified ) % simplified.size();
QString abbrName;
if ( !Utils::isCJKChar( simplified.at( index ).unicode() ) ) {
// take two chars.
abbrName = simplified.mid( index, 2 );
if ( abbrName.size() == 1 ) {
//make up two characters.
abbrName = abbrName + simplified.at( 0 );
}
}
else {
abbrName = simplified.mid( index, 1 );
}
return abbrName;
return GlobalBroadcaster::instance()->getAbbrName( text );
}
void Class::isolateCSS( QString & css, QString const & wrapperSelector )

View file

@ -318,10 +318,10 @@ protected:
// Load icon from filename directly if isFullName == true
// else treat filename as name without extension
bool loadIconFromFile( QString const & filename, bool isFullName = false );
bool loadIconFromText( QString iconUrl, QString const & text );
QString getAbbrName( QString const & text );
bool loadIconFromText( const QString & iconUrl, QString const & text );
static QString getAbbrName( QString const & text );
static QColor intToFixedColor( int index );
/// Make css content usable only for articles from this dictionary
void isolateCSS( QString & css, QString const & wrapperSelector = QString() );

View file

@ -592,12 +592,49 @@ public:
cancel();
} );
connect( this, &DictServerArticleRequest::finishedArticle, this, [ this ]( QString articleText ) {
connect( this, &DictServerArticleRequest::finishedArticle, this, [ this ]( QString _articleText ) {
if ( Utils::AtomicInt::loadAcquire( isCancelled ) ) {
cancel();
return;
}
//modify the _articleText,remove extra lines[start with 15X etc.]
QList< QString > lines = _articleText.split( "\n", Qt::SkipEmptyParts );
QString resultStr;
// process the line
static QRegularExpression leadingRespCode( "^\\d{3} " );
uint32_t leadingSpaceCount = 0;
uint32_t firstLeadingSpaceCount = 0;
for ( const QString & line : lines ) {
//ignore 15X lines
if ( leadingRespCode.match( line ).hasMatch() ) {
continue;
}
// ignore dot(.),the end line character
if ( line.trimmed() == "." ) {
break;
}
auto lsc = Utils::leadingSpaceCount( line );
if ( firstLeadingSpaceCount == 0 && lsc > firstLeadingSpaceCount ) {
firstLeadingSpaceCount = lsc;
}
if ( lsc >= leadingSpaceCount && lsc > firstLeadingSpaceCount ) {
//extra space
resultStr.append( " " );
resultStr.append( line.trimmed() );
}
else {
resultStr.append( "\n" );
resultStr.append( line );
}
leadingSpaceCount = lsc;
}
static QRegularExpression phonetic( R"(\\([^\\]+)\\)",
QRegularExpression::CaseInsensitiveOption ); // phonetics: \stuff\ ...
static QRegularExpression divs_inside_phonetic( "</div([^>]*)><div([^>]*)>",
@ -610,26 +647,26 @@ public:
string articleStr;
if ( contentInHtml ) {
articleStr = articleText.toUtf8().data();
articleStr = resultStr.toUtf8().data();
}
else {
articleStr = Html::preformat( articleText.toUtf8().data() );
articleStr = Html::preformat( resultStr.toUtf8().data() );
}
articleText = QString::fromUtf8( articleStr.c_str(), articleStr.size() );
_articleText = QString::fromUtf8( articleStr.c_str(), articleStr.size() );
int pos;
if ( !contentInHtml ) {
articleText = articleText.replace( refs, R"(<a href="gdlookup://localhost/\1">\1</a>)" );
_articleText = _articleText.replace( refs, R"(<a href="gdlookup://localhost/\1">\1</a>)" );
pos = 0;
QString articleNewText;
// Handle phonetics
QRegularExpressionMatchIterator it = phonetic.globalMatch( articleText );
QRegularExpressionMatchIterator it = phonetic.globalMatch( _articleText );
while ( it.hasNext() ) {
QRegularExpressionMatch match = it.next();
articleNewText += articleText.mid( pos, match.capturedStart() - pos );
articleNewText += _articleText.mid( pos, match.capturedStart() - pos );
pos = match.capturedEnd();
QString phonetic_text = match.captured( 1 );
@ -638,18 +675,18 @@ public:
articleNewText += R"(<span class="dictd_phonetic">)" + phonetic_text + "</span>";
}
if ( pos ) {
articleNewText += articleText.mid( pos );
articleText = articleNewText;
articleNewText += _articleText.mid( pos );
_articleText = articleNewText;
articleNewText.clear();
}
// Handle links
pos = 0;
it = links.globalMatch( articleText );
it = links.globalMatch( _articleText );
while ( it.hasNext() ) {
QRegularExpressionMatch match = it.next();
articleNewText += articleText.mid( pos, match.capturedStart() - pos );
articleNewText += _articleText.mid( pos, match.capturedStart() - pos );
pos = match.capturedEnd();
QString link = match.captured( 1 );
@ -663,13 +700,13 @@ public:
articleNewText += newLink;
}
if ( pos ) {
articleNewText += articleText.mid( pos );
articleText = articleNewText;
articleNewText += _articleText.mid( pos );
_articleText = articleNewText;
articleNewText.clear();
}
}
articleData += string( "<div class=\"dictd_article\">" ) + articleText.toUtf8().data() + "<br></div>";
articleData += string( "<div class=\"dictd_article\">" ) + _articleText.toUtf8().data() + "<br></div>";
if ( !articleData.empty() ) {

View file

@ -877,8 +877,7 @@ QString & MdxDictionary::filterResource( QString & article )
void MdxDictionary::replaceLinks( QString & id, QString & article )
{
QString articleNewText;
qsizetype linkPos = 0;
int linkPos = 0;
QRegularExpressionMatchIterator it = RX::Mdx::allLinksRe.globalMatch( article );
while ( it.hasNext() ) {
QRegularExpressionMatch allLinksMatch = it.next();
@ -954,14 +953,13 @@ void MdxDictionary::replaceLinks( QString & id, QString & article )
articleNewText += linkTxt;
match = RX::Mdx::closeScriptTagRe.match( article, linkPos );
if ( match.hasMatch() ) {
articleNewText += QString( QStringLiteral( "gdOnReady(()=>{%1});</script>" ) )
.arg( article.mid( linkPos, match.capturedStart() - linkPos ) );
articleNewText += article.mid( linkPos, match.capturedEnd() - linkPos );
linkPos = match.capturedEnd();
}
continue;
}
else {
//audio ,script,video ,html5 tags fall here.
//audio ,video ,html5 tags fall here.
match = RX::Mdx::srcRe.match( linkTxt );
if ( match.hasMatch() ) {
QString newText;
@ -973,15 +971,9 @@ void MdxDictionary::replaceLinks( QString & id, QString & article )
else {
scheme = "bres://";
}
newText =
match.captured( 1 ) + match.captured( 2 ) + scheme + id + "/" + match.captured( 3 ) + match.captured( 2 );
//add defer to script tag
if ( linkType.compare( "script" ) == 0 ) {
newText = newText + " defer ";
}
newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText );
}
else {

View file

@ -275,14 +275,18 @@ quint32 LangCoder::guessId( const QString & lang )
std::pair< quint32, quint32 > LangCoder::findLangIdPairFromName( QString const & name )
{
static QRegularExpression reg( "(?=([a-z]{2,3})-([a-z]{2,3}))", QRegularExpression::CaseInsensitiveOption );
static QRegularExpression reg( "(^|[^a-z])((?<lang1>[a-z]{2,3})-(?<lang2>[a-z]{2,3}))($|[^a-z])",
QRegularExpression::CaseInsensitiveOption );
auto matches = reg.globalMatch( name );
while ( matches.hasNext() ) {
auto m = matches.next();
if ( matches.hasNext() ) {
continue; // We use only the last match, skip previous ones
}
auto fromId = guessId( m.captured( 1 ).toLower() );
auto toId = guessId( m.captured( 2 ).toLower() );
auto fromId = guessId( m.captured( "lang1" ).toLower() );
auto toId = guessId( m.captured( "lang2" ).toLower() );
if ( code2Exists( intToCode2( fromId ) ) && code2Exists( intToCode2( toId ) ) ) {
return { fromId, toId };

View file

@ -1,11 +1,3 @@
function gdOnReady(func) {
if (document.readyState !== "loading") {
func();
} else {
document.addEventListener("DOMContentLoaded", func);
}
}
function gdMakeArticleActive(newId, noEvent) {
const gdCurrentArticle =
document.querySelector(".gdactivearticle").attributes.id;

View file

@ -4025,7 +4025,7 @@ void MainWindow::on_importFavorites_triggered()
QString fileName = QFileDialog::getOpenFileName( this,
tr( "Import Favorites from file" ),
importPath,
tr( "XML files (*.xml);;Txt files (*.txt);;All files (*.*)" ) );
tr( "Text and XML files (*.txt *.xml);;All files (*.*)" ) );
if ( fileName.size() == 0 ) {
return;
}

View file

@ -10,9 +10,9 @@ Additionally, multiple strategies of automatic grouping are provided:
## Auto groups by dictionary language
For formats like DSL, which has embedded language from / to metadata, GoldenDict will use the dictionary's built-in metadata.
For formats like DSL, which has embedded language from / to metadata, GD will use the dictionary's built-in metadata.
For other formats, GoldenDict will try to extract languages from the dictionary's name or its file name by finding `{id}-{id}` pair. The `{id}` is 2 or 3 letters ISO 639 codes. For example, if a dictionary named `some name en-zh`, it will be automatically grouped into `en-zh`.
For other formats, GD will try finding the last `{id}-{id}` pair delimited by non-alphabets in dictionary name or main file name to extract languages. The `{id}` is 2 or 3 letters ISO 639 codes. For example, if a dictionary named `some name en-zh`, it will be automatically grouped into `en-zh`.
Groups created in this method also include a context menu when right-click the group name, in which you can do additional dictionaries grouping by source or target language and combine dictionaries in more large groups.