Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions Src/Common/RootSite/RootSiteTests/RenderBenchmarkTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace SIL.FieldWorks.Common.RootSites.RenderBenchmark
public abstract class RenderBenchmarkTestsBase : RealDataTestsBase
{
protected const string DeterministicRenderFontFamily = "Segoe UI";
// Second Latin font for scenarios whose runs alternate font family.
protected const string SecondaryRenderFontFamily = "Times New Roman";
// Pinned Arabic font (loaded privately by RenderTestAssemblySetup). Used for Arabic runs so
// they don't depend on the host's Segoe UI Arabic version / font fallback.
protected const string ArabicRenderFontFamily = "Scheherazade New";
Expand Down Expand Up @@ -320,6 +322,15 @@ protected void SetupScenarioData(string scenarioId)
case "multi-ws":
CreateMultiWsScenario();
break;
case "single-para-mixed-ws":
CreateSingleParaMixedWsScenario();
break;
case "nfc-composable-diacritics":
CreateNfcComposableDiacriticsScenario();
break;
case "multi-line-wrap-single-ws":
CreateMultiLineWrapSingleWsScenario();
break;
case "lex-shallow":
CreateLexEntryScenario(depth: 2, breadth: 3);
break;
Expand Down Expand Up @@ -539,6 +550,27 @@ private void CreateMultiWsScenario()
AddMultiWsSections(book, 5, versesPerSection: 8, chapterStart: 1);
}

private void CreateSingleParaMixedWsScenario()
{
var book = CreateBook(19); // PSA
m_hvoRoot = book.Hvo;
AddSingleMixedWsParagraph(book, sentenceCount: 236);
}

private void CreateNfcComposableDiacriticsScenario()
{
var book = CreateBook(15); // EZR
m_hvoRoot = book.Hvo;
AddNfcComposableDiacriticsParagraph(book, wordCount: 80);
}

private void CreateMultiLineWrapSingleWsScenario()
{
var book = CreateBook(17); // EST
m_hvoRoot = book.Hvo;
AddSingleWsProseParagraph(book, sentenceCount: 200);
}

#region Rich Data Factories

protected IScrBook CreateBook(int bookNum)
Expand Down Expand Up @@ -928,6 +960,147 @@ protected void AddMultiWsSections(IScrBook book, int sectionCount,
}
}

/// <summary>
/// Returns the canonical decomposition (NFD) of <paramref name="text"/>, and fails the
/// fixture unless the result contains combining marks that NFC would compose. A scenario
/// built from it therefore always exercises text that normalization rewrites, whatever
/// normalization form the source file itself is stored in.
/// </summary>
protected static string Decomposed(string text)
{
string nfd = CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFD).Normalize(text);
string nfc = CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFC).Normalize(text);
Assert.That(nfd, Is.Not.EqualTo(nfc),
$"Scenario text '{text}' must contain characters that decompose.");
return nfd;
}

/// <summary>
/// Adds a section whose content is one paragraph of <paramref name="sentenceCount"/>
/// sentences. Each sentence's text and run properties come from the two callbacks, which
/// receive the sentence index. With <paramref name="withChapterNumber"/> the paragraph
/// opens with a chapter-number run.
/// </summary>
private void AddSingleParagraphSection(IScrBook book, string heading, int sentenceCount,
Func<int, string> sentence, Func<int, ITsTextProps> runProps, bool withChapterNumber = false)
{
var section = Cache.ServiceLocator.GetInstance<IScrSectionFactory>().Create();
book.SectionsOS.Add(section);

var stTextFactory = Cache.ServiceLocator.GetInstance<IStTextFactory>();

section.HeadingOA = stTextFactory.Create();
var headingBldr = new StTxtParaBldr(Cache) { ParaStyleName = ScrStyleNames.SectionHead };
headingBldr.AppendRun(heading, StyleUtils.CharStyleTextProps(null, m_wsEng));
headingBldr.CreateParagraph(section.HeadingOA);

section.ContentOA = stTextFactory.Create();
var paraBldr = new StTxtParaBldr(Cache) { ParaStyleName = ScrStyleNames.NormalParagraph };
if (withChapterNumber)
paraBldr.AppendRun("1", StyleUtils.CharStyleTextProps(ScrStyleNames.ChapterNumber, m_wsEng));

for (int i = 0; i < sentenceCount; i++)
paraBldr.AppendRun(sentence(i), runProps(i));

paraBldr.CreateParagraph(section.ContentOA);
}

/// <summary>
/// Adds one paragraph of short decomposed sentences whose runs alternate between two
/// writing systems and two font families.
/// </summary>
protected void AddSingleMixedWsParagraph(IScrBook book, int sentenceCount)
{
string[] subjects =
{
"the \u00E9lder",
"the h\u00E8rder",
"the s\u00EFnger",
"the tea\u00E7her",
"the trav\u00EAler",
"the wom\u00E3n"
};
string[] predicates =
{
"spoke of the l\u00F3ng rains",
"walked to the f\u00E0r well",
"named the sev\u00EBn hills",
"counted the cattl\u00E9 at dusk",
"kept the \u00F4ld story",
"asked for a bless\u0129ng"
};

AddSingleParagraphSection(book, "Single Paragraph, Mixed Writing Systems", sentenceCount,
i => Decomposed($"{subjects[i % subjects.Length]} {predicates[i % predicates.Length]} {i + 1}. "),
i => AlternatingFontRunProps(i % 2 == 0),
withChapterNumber: true);
}

private ITsTextProps AlternatingFontRunProps(bool first)
{
var bldr = TsStringUtils.MakePropsBldr();
bldr.SetIntPropValues((int)FwTextPropType.ktptWs, (int)FwTextPropVar.ktpvDefault,
first ? m_wsEng : m_wsFr);
bldr.SetStrPropValue((int)FwTextPropType.ktptFontFamily,
first ? DeterministicRenderFontFamily : SecondaryRenderFontFamily);
return bldr.GetTextProps();
}

/// <summary>
/// Adds one wrapped paragraph of Latin sentences, each naming a word spelled with
/// decomposed diacritics.
/// </summary>
protected void AddNfcComposableDiacriticsParagraph(IScrBook book, int wordCount)
{
string[] words =
{
"caf\u00E9",
"d\u00E9j\u00E0",
"no\u00EBl",
"fran\u00E7ais",
"gar\u00E7on",
"h\u00F4tel",
"a\u00F1o",
"cr\u00E9\u00E9e",
"\u00E9l\u00E9gant",
"fa\u00E7ade"
};

AddSingleParagraphSection(book, "Decomposed Diacritics Microbenchmark", wordCount,
i => Decomposed($"The {words[i % words.Length]} recorded here is entry {i + 1}. "),
i => StyleUtils.CharStyleTextProps(null, m_wsEng));
}

/// <summary>
/// Adds one long wrapped paragraph of decomposed sentences in a single writing system
/// and font.
/// </summary>
protected void AddSingleWsProseParagraph(IScrBook book, int sentenceCount)
{
string[] subjects =
{
"the mer\u00E7hant",
"the mas\u00F3n",
"the scrib\u00EB",
"the sheph\u00E8rd",
"the weav\u00EAr",
"the pott\u00E9r"
};
string[] predicates =
{
"measured the grain by the riv\u00E9r",
"repaired the east\u00E8rn wall before dusk",
"copied the ledger onto fresh parchm\u00EBnt",
"counted the flock past the old gat\u00EA",
"dyed the cloth a deep saffr\u00F5n",
"shaped the jar on the slow whe\u00EBl"
};

AddSingleParagraphSection(book, "Single Writing-System Line-Wrap Microbenchmark", sentenceCount,
i => Decomposed($"{subjects[i % subjects.Length]} {predicates[i % predicates.Length]} on day {i + 1}. "),
i => StyleUtils.CharStyleTextProps(null, m_wsEng));
}

#endregion

#region Lex Entry Scenario Data
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@
"description": "Lex entry with senses nested 6 levels deep, 2-wide (depth 6, breadth 2 = 126 senses)",
"tags": ["lex-entry", "nested-senses", "exponential-cost", "stress"],
"viewType": "LexEntry"
},
{
"id": "single-para-mixed-ws",
"description": "One paragraph of 236 short decomposed (NFD) sentences whose runs alternate two writing systems and two font families",
"tags": ["stress", "layout-stress", "multi-ws", "single-paragraph", "nfd"]
},
{
"id": "nfc-composable-diacritics",
"description": "One wrapped paragraph of Latin prose spelled with decomposed (NFD) diacritics that NFC composes",
"tags": ["stress", "layout-stress", "nfd", "diacritics", "line-breaking"]
},
{
"id": "multi-line-wrap-single-ws",
"description": "One long paragraph of 200 decomposed (NFD) sentences in one writing system and font, wrapping over many lines",
"tags": ["stress", "layout-stress", "single-paragraph", "line-breaking", "nfd"]
}
]
}
89 changes: 89 additions & 0 deletions Src/views/Test/TestUniscribeEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Last reviewed:

#include "testViews.h"
#include "RenderEngineTestBase.h"
#include "LayoutCache.h"

namespace TestViews
{
Expand Down Expand Up @@ -396,6 +397,94 @@ namespace TestViews
#endif
}

// Installs a fresh layout-pass cache for the lifetime of the object, restoring the
// previous one even when a test assertion throws.
class ScopedLayoutPassCache
{
public:
ScopedLayoutPassCache() : m_pPrev(SetCurrentLayoutPassCache(&m_cache)) {}
~ScopedLayoutPassCache() { SetCurrentLayoutPassCache(m_pPrev); }
LayoutPassCache & Cache() { return m_cache; }

private:
LayoutPassCache m_cache;
LayoutPassCache * m_pPrev;
};

// Breaks one text source twice in one layout pass and reports cache hits and misses.
// The engine is built here, not via COM, so it sees the cache this module installs.
void BreakTwiceInOneLayoutPass(const wchar_t * pszText, int * pcHit, int * pcMiss)
{
*pcHit = *pcMiss = 0;
#if defined(WIN32) || defined(_M_X64)
HWND hwndDesktop = ::GetDesktopWindow();
HDC hdcDesktop = ::GetDC(hwndDesktop);
HDC hdc = ::CreateCompatibleDC(hdcDesktop);
HBITMAP hbm = ::CreateCompatibleBitmap(hdcDesktop, 100, 100);
HGDIOBJ hbmOld = ::SelectObject(hdc, hbm);

IVwGraphicsWin32Ptr qvg;
qvg.CreateInstance(CLSID_VwGraphicsWin32);
qvg->Initialize(hdc);

ILgWritingSystemFactoryPtr qwsf;
m_qre->get_WritingSystemFactory(&qwsf);
IRenderEnginePtr qre;
UniscribeEngine::CreateCom(NULL, IID_IRenderEngine, (void **)&qre);
CheckHr(qre->putref_WritingSystemFactory(qwsf));
CheckHr(qre->putref_RenderEngineFactory(m_qref));
TxtSrc ts(pszText, qwsf);
IVwTextSourcePtr qts;
ts.QueryInterface(IID_IVwTextSource, (void **)&qts);
int cch;
CheckHr(qts->get_Length(&cch));

{
ScopedLayoutPassCache scope;
for (int ibreak = 0; ibreak < 2; ++ibreak)
{
ILgSegmentPtr qseg;
int dichLimSeg;
int dxWidth;
LgEndSegmentType est;
CheckHr(qre->FindBreakPoint(qvg, qts, NULL, 0, cch, cch, TRUE, TRUE, 4000,
klbWordBreak, klbLetterBreak, ktwshAll, FALSE, &qseg, &dichLimSeg,
&dxWidth, &est, NULL));
}
*pcHit = scope.Cache().AnalysisCache().HitCount();
*pcMiss = scope.Cache().AnalysisCache().MissCount();
}

qts.Clear();
qre.Clear();
qvg.Clear();
::SelectObject(hdc, hbmOld);
::DeleteObject(hbm);
::DeleteDC(hdc);
::ReleaseDC(hwndDesktop, hdcDesktop);
#endif
}

void testNfcTextAnalysisIsReusedWithinLayoutPass()
{
#if defined(WIN32) || defined(_M_X64)
int cHit, cMiss;
BreakTwiceInOneLayoutPass(L"cafe deja vu", &cHit, &cMiss);
unitpp::assert_true("text NFC leaves unchanged should be reused on the second break",
cHit > 0);
#endif
}

void testDecomposedTextAnalysisIsReusedWithinLayoutPass()
{
#if defined(WIN32) || defined(_M_X64)
int cHit, cMiss;
// Decomposed e-acute and a-grave, which NFC composes.
BreakTwiceInOneLayoutPass(L"cafe\u0301 de\u0301ja\u0300 vu", &cHit, &cMiss);
unitpp::assert_true("text NFC rewrites should be reused on the second break", cHit > 0);
#endif
}

virtual void Setup()
{
RenderEngineTestBase::Setup();
Expand Down
Loading
Loading