harfbuzz/src/graphite2/tests/comparerenderer/CompareRenderer.cpp

389 lines
13 KiB
C++

// SPDX-License-Identifier: MIT OR MPL-2.0 OR GPL-2.0-or-later-2.1-or-later
// Copyright 2010, SIL International, All rights reserved.
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef __GNUC__
#include <unistd.h>
#ifndef __linux__
#include <sys/time.h>
#endif
#endif
#ifdef WIN32
#include <windows.h>
#endif
#include "RendererOptions.h"
#include "Renderer.h"
#include "RenderedLine.h"
#include "FeatureParser.h"
#include "Gr2Renderer.h"
#include "graphite2/Log.h"
const size_t NUM_RENDERERS = 6;
class CompareRenderer
{
public:
CompareRenderer(const char * testFile, Renderer** renderers, bool verbose)
: m_fileBuffer(NULL), m_fileLength(0), m_numLines(0), m_lineOffsets(NULL),
m_renderers(renderers), m_verbose(verbose), m_cfMask(ALL_DIFFERENCE_TYPES)
{
// read the file into memory for fast access
struct stat fileStat;
if (stat(testFile, &fileStat) == 0)
{
FILE * file = fopen(testFile, "rb");
if (file)
{
m_fileBuffer = new char[fileStat.st_size];
if (m_fileBuffer)
{
m_fileLength = fread(m_fileBuffer, 1, fileStat.st_size, file);
assert(m_fileLength == fileStat.st_size);
countLines();
findLines();
for (size_t r = 0; r < NUM_RENDERERS; r++)
{
if (m_renderers[r])
{
m_lineResults[r] = new RenderedLine[m_numLines];
}
else
{
m_lineResults[r] = NULL;
}
m_elapsedTime[r] = 0.0f;
m_glyphCount[r] = 0;
}
}
fclose(file);
}
else
{
fprintf(stderr, "Error opening file %s\n", testFile);
}
}
else
{
fprintf(stderr, "Error stating file %s\n", testFile);
for (size_t r = 0; r < NUM_RENDERERS; r++)
{
m_lineResults[r] = NULL;
m_elapsedTime[r] = 0.0f;
}
}
}
~CompareRenderer()
{
delete [] m_fileBuffer;
m_fileBuffer = NULL;
for (size_t i = 0; i < NUM_RENDERERS; i++)
{
if (m_lineResults[i]) delete [] m_lineResults[i];
m_lineResults[i] = NULL;
}
if (m_lineOffsets) delete [] m_lineOffsets;
m_lineOffsets = NULL;
}
void runTests(FILE * log, int repeat = 1)
{
for (size_t r = 0; r < NUM_RENDERERS; r++)
{
if (m_renderers[r])
{
for (int i = 0; i < repeat; i++)
m_elapsedTime[r] += runRenderer(*m_renderers[r], m_lineResults[r], m_glyphCount[r], log);
fprintf(stdout, "Ran %s in %fs (%lu glyphs)\n", m_renderers[r]->name(), m_elapsedTime[r], m_glyphCount[r]);
}
}
}
int compare(float tolerance, float fractionalTolerance, FILE * log)
{
int status = IDENTICAL;
for (size_t i = 0; i < NUM_RENDERERS; i++)
{
for (size_t j = i + 1; j < NUM_RENDERERS; j++)
{
if (m_renderers[i] == NULL || m_renderers[j] == NULL) continue;
if (m_lineResults[i] == NULL || m_lineResults[j] == NULL) continue;
fprintf(log, "Comparing %s with %s\n", m_renderers[i]->name(), m_renderers[j]->name());
for (size_t line = 0; line < m_numLines; line++)
{
LineDifference ld = m_lineResults[i][line].compare(m_lineResults[j][line], tolerance, fractionalTolerance);
ld = (LineDifference)(m_cfMask & ld);
if (ld)
{
fprintf(log, "Line %u %s\n", (unsigned int)line, DIFFERENCE_DESC[ld]);
for (size_t c = m_lineOffsets[line]; c < m_lineOffsets[line+1]; c++)
{
fprintf(log, "%c", m_fileBuffer[c]);
}
fprintf(log, "\n");
m_lineResults[i][line].dump(log);
fprintf(log, "%s\n", m_renderers[i]->name());
m_lineResults[j][line].dump(log);
fprintf(log, "%s\n", m_renderers[j]->name());
status |= ld;
}
}
}
}
return status;
}
void setDifferenceMask(LineDifference m) { m_cfMask = m; }
protected:
float runRenderer(Renderer & renderer, RenderedLine * pLineResult, unsigned long & glyphCount, FILE *log)
{
glyphCount = 0;
unsigned int i = 0;
const char * pLine = m_fileBuffer;
#ifdef __linux__
struct timespec startTime;
struct timespec endTime;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &startTime);
#else
#ifdef WIN32
LARGE_INTEGER counterFreq;
LARGE_INTEGER startCounter;
LARGE_INTEGER endCounter;
if (!QueryPerformanceFrequency(&counterFreq))
fprintf(stderr, "Warning no high performance counter available\n");
QueryPerformanceCounter(&startCounter);
#else
struct timeval startTime;
struct timeval endTime;
gettimeofday(&startTime,0);
#endif
#endif
// check for CRLF
int lfLength = 1;
if ((m_numLines > 1) && (m_lineOffsets[1] > 2) && (m_fileBuffer[m_lineOffsets[1]-2] == '\r'))
lfLength = 2;
if (m_verbose)
{
fprintf(log, "[\n");
while (i < m_numLines)
{
size_t lineLength = m_lineOffsets[i+1] - m_lineOffsets[i] - lfLength;
pLine = m_fileBuffer + m_lineOffsets[i];
renderer.renderText(pLine, lineLength, pLineResult + i, log);
pLineResult[i].dump(log);
glyphCount += pLineResult[i].numGlyphs();
++i;
}
fprintf(log, "]\n");
}
else
{
while (i < m_numLines)
{
size_t lineLength = m_lineOffsets[i+1] - m_lineOffsets[i] - lfLength;
pLine = m_fileBuffer + m_lineOffsets[i];
renderer.renderText(pLine, lineLength, pLineResult + i, log);
glyphCount += pLineResult[i].numGlyphs();
++i;
}
}
float elapsed = 0.;
#ifdef __linux__
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &endTime);
long deltaSeconds = endTime.tv_sec - startTime.tv_sec;
long deltaNs = endTime.tv_nsec - startTime.tv_nsec;
if (deltaNs < 0)
{
deltaSeconds -= 1;
deltaNs += 1000000000;
}
elapsed = deltaSeconds + deltaNs / 1000000000.0f;
#else
#ifdef WIN32
QueryPerformanceCounter(&endCounter);
elapsed = (endCounter.QuadPart - startCounter.QuadPart) / static_cast<float>(counterFreq.QuadPart);
#else
gettimeofday(&endTime,0);
long deltaSeconds = endTime.tv_sec - startTime.tv_sec;
long deltaUs = endTime.tv_usec - startTime.tv_usec;
if (deltaUs < 0)
{
deltaSeconds -= 1;
deltaUs += 1000000;
}
elapsed = deltaSeconds + deltaUs / 1000000.0f;
#endif
#endif
return elapsed;
}
size_t countLines()
{
for (size_t i = 0; i < m_fileLength; i++)
{
if (m_fileBuffer[i] == '\n')
{
++m_numLines;
}
}
return m_numLines;
}
void findLines()
{
m_lineOffsets = new size_t[m_numLines+1];
m_lineOffsets[0] = 0;
int line = 0;
for (size_t i = 0; i < m_fileLength; i++)
{
if (m_fileBuffer[i] == '\n')
{
m_lineOffsets[++line] = i + 1;
}
if (m_fileBuffer[i] > 0 && m_fileBuffer[i] < 32)
m_fileBuffer[i] = 32;
}
m_lineOffsets[m_numLines] = m_fileLength;
}
private:
char * m_fileBuffer;
size_t m_fileLength;
size_t m_numLines;
size_t * m_lineOffsets;
Renderer** m_renderers;
RenderedLine * m_lineResults[NUM_RENDERERS];
float m_elapsedTime[NUM_RENDERERS];
unsigned long m_glyphCount[NUM_RENDERERS];
bool m_verbose;
LineDifference m_cfMask;
};
int main(int argc, char ** argv)
{
if (!parseOptions(argc, argv) ||
!rendererOptions[OptFontFile].exists() ||
!rendererOptions[OptTextFile].exists() ||
!rendererOptions[OptSize].exists())
{
fprintf(stderr, "Usage:\n%s [options] -t utf8.txt -f font.ttf -s 12\n", argv[0]);
fprintf(stderr, "Options:\n");
showOptions();
return -1;
}
const char * textFile = rendererOptions[OptTextFile].get(argv);
const char * fontFile = rendererOptions[OptFontFile].get(argv);
int fontSize = rendererOptions[OptSize].getInt(argv);
FILE * log = stdout;
if (rendererOptions[OptLogFile].exists())
{
log = fopen(rendererOptions[OptLogFile].get(argv), "wb");
if (!log)
{
fprintf(stderr, "Failed to open log file %s\n",
rendererOptions[OptLogFile].get(argv));
return -2;
}
}
Renderer* renderers[NUM_RENDERERS] = {NULL, NULL, NULL, NULL, NULL};
FeatureParser * featureSettings = NULL;
FeatureParser * altFeatureSettings = NULL;
bool direction = rendererOptions[OptRtl].exists();
const std::string traceLogPath = rendererOptions[OptTrace].exists() ? rendererOptions[OptTrace].get(argv) : std::string();
Gr2Face face(fontFile, traceLogPath, rendererOptions[OptDemand].get(argv));
if (rendererOptions[OptFeatures].exists())
{
featureSettings = new FeatureParser(rendererOptions[OptFeatures].get(argv));
}
if (rendererOptions[OptQuiet].exists())
{
fclose(stderr);
}
if (rendererOptions[OptAlternativeFont].exists())
{
if (rendererOptions[OptAltFeatures].exists())
{
altFeatureSettings = new FeatureParser(rendererOptions[OptAltFeatures].get(argv));
}
else
{
altFeatureSettings = featureSettings;
}
const char * altFontFile = rendererOptions[OptAlternativeFont].get(argv);
if (rendererOptions[OptGraphite2].exists())
{
std::string altTraceLogPath = traceLogPath;
altTraceLogPath.insert(traceLogPath.find_last_of('.'), ".alt");
Gr2Face altFace(altFontFile, altTraceLogPath, rendererOptions[OptDemand].get(argv));
renderers[0] = new Gr2Renderer(face, fontSize, direction, featureSettings);
renderers[1] = new Gr2Renderer(altFace, fontSize, direction, altFeatureSettings);
}
}
else
{
if (rendererOptions[OptGraphite2].exists())
renderers[0] = new Gr2Renderer(face, fontSize, direction, featureSettings);
if (rendererOptions[OptGraphite2s].exists())
{
Gr2Face uncached(fontFile,
std::string(traceLogPath).insert(traceLogPath.find_last_of('.'), ".uncached"), rendererOptions[OptDemand].get(argv));
renderers[1] = new Gr2Renderer(uncached, fontSize, direction, featureSettings);
}
}
if (renderers[0] == NULL && renderers[1] == NULL)
{
fprintf(stderr, "Please specify at least 1 renderer\n");
showOptions();
if (rendererOptions[OptLogFile].exists()) fclose(log);
return -3;
}
CompareRenderer compareRenderers(textFile, renderers, rendererOptions[OptVerbose].exists());
if (rendererOptions[OptRepeat].exists())
compareRenderers.runTests(log, rendererOptions[OptRepeat].getInt(argv));
else
compareRenderers.runTests(log);
// set compare options
if (rendererOptions[OptIgnoreGlyphIdDifferences].exists())
{
compareRenderers.setDifferenceMask((LineDifference)(ALL_DIFFERENCE_TYPES ^ DIFFERENT_GLYPHS));
}
int status = 0;
if (rendererOptions[OptCompare].exists())
status = compareRenderers.compare(rendererOptions[OptTolerance].getFloat(argv),
rendererOptions[OptFractionalTolerance].getFloat(argv), log);
for (size_t i = 0; i < NUM_RENDERERS; i++)
{
if (renderers[i])
{
delete renderers[i];
renderers[i] = NULL;
}
}
if (altFeatureSettings != featureSettings)
delete altFeatureSettings;
delete featureSettings;
if (rendererOptions[OptLogFile].exists()) fclose(log);
return status;
}