harfbuzz/src/graphite2/gr2fonttest/gr2FontTest.cpp

781 lines
24 KiB
C++

// SPDX-License-Identifier: MIT
// Copyright (C) 2005 www.thanlwinsoft.org, SIL International
/*
Responsibility: Keith Stribley, Martin Hosken
Description:
A simple console app that creates a segment using FileFont and dumps a
diagnostic table of the resulting glyph vector to the console.
If graphite has been built with -DTRACING then it will also produce a
diagnostic log of the segment creation in grSegmentLog.txt
*/
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <climits>
#include <iomanip>
#include <cstring>
#include "UtfCodec.h"
#include "graphite2/Types.h"
#include "graphite2/Segment.h"
#include "graphite2/Log.h"
class Gr2TextSrc
{
public:
Gr2TextSrc(const uint32_t* base, size_t len) : m_buff(base), m_len(len) { }
gr_encform utfEncodingForm() const { return gr_utf32; }
size_t getLength() const { return m_len; }
const void* get_utf_buffer_begin() const { return m_buff; }
private:
const uint32_t* m_buff;
size_t m_len;
};
#ifndef HAVE_STRTOF
float strtof(char * text, char ** /*ignore*/)
{
return static_cast<float>(atof(text));
}
#endif
#ifndef HAVE_STRTOL
long strtol(char * text, char ** /*ignore*/)
{
return atol(text);
}
#endif
class Parameters
{
public:
Parameters();
~Parameters();
void clear();
void closeLog();
bool loadFromArgs(int argc, char *argv[]);
int testFileFont() const;
gr_feature_val* parseFeatures(const gr_face * face) const;
void printFeatures(const gr_face * face) const;
public:
const char * fileName;
const char * features;
float pointSize;
int dpi;
bool lineStart;
bool lineEnd;
bool ws;
bool rtl;
bool useLineFill;
bool noprint;
int useCodes;
bool autoCodes;
int justification;
float width;
int textArgIndex;
unsigned int * pText32;
size_t charLength;
size_t offset;
FILE * log;
char * trace;
char * alltrace;
int codesize;
gr_face_options opts;
private : //defensive since log should not be copied
Parameters(const Parameters&);
Parameters& operator=(const Parameters&);
};
Parameters::Parameters()
{
log = stdout ;
clear();
}
Parameters::~Parameters()
{
free(pText32);
pText32 = NULL;
closeLog();
}
void Parameters::clear()
{
closeLog() ;
fileName = "";
features = nullptr;
pointSize = 12.0f;
dpi = 72;
lineStart = false;
lineEnd = false;
rtl = false;
ws = false;
useLineFill = false;
useCodes = 0;
autoCodes = false;
noprint = false;
justification = 0;
width = 100.0f;
pText32 = NULL;
textArgIndex = 0;
charLength = 0;
codesize = 4;
offset = 0;
log = stdout;
trace = NULL;
alltrace = NULL;
opts = gr_face_preloadAll;
}
void Parameters::closeLog()
{
if (log==stdout)
return ;
fclose(log);
log = stdout;
}
int lookup(size_t *map, size_t val);
namespace gr2 = graphite2;
template <typename utf>
size_t convertUtf(const void * src, unsigned int * & dest)
{
dest = static_cast<unsigned int *>(malloc(sizeof(unsigned int)*(strlen(reinterpret_cast<const char *>(src))+1)));
if (!dest)
return 0;
typename utf::const_iterator ui = src;
unsigned int * out = dest;
while ((*out = *ui) != 0 && !ui.error())
{
++ui;
++out;
}
if (ui.error())
{
free(dest);
dest = 0;
return size_t(-1);
}
return (out-dest);
}
bool Parameters::loadFromArgs(int argc, char *argv[])
{
int mainArgOffset = 0;
pText32 = NULL;
features = NULL;
log = stdout;
codesize = 4;
bool argError = false;
char* pText = NULL;
typedef enum
{
NONE,
POINT_SIZE,
DPI,
JUSTIFY,
LINE_START,
LINE_END,
LINE_FILL,
CODES,
AUTOCODES,
FEAT,
LOG,
TRACE,
ALLTRACE,
SIZE
} TestOptions;
TestOptions option = NONE;
char * pIntEnd = NULL;
char * pFloatEnd = NULL;
long lTestSize = 0;
float fTestSize = 0.0f;
for (int a = 1; a < argc; a++)
{
switch (option)
{
case DPI:
pIntEnd = NULL;
lTestSize = strtol(argv[a],&pIntEnd, 10);
if (lTestSize > 0 && lTestSize < INT_MAX && lTestSize != LONG_MAX)
{
dpi = lTestSize;
}
else
{
fprintf(stderr,"Invalid dpi %s\n", argv[a]);
}
option = NONE;
break;
case POINT_SIZE:
pFloatEnd = NULL;
fTestSize = strtof(argv[a],&pFloatEnd);
// what is a reasonable maximum here
if (fTestSize > 0 && fTestSize < 5000.0f)
{
pointSize = fTestSize;
}
else
{
fprintf(stderr,"Invalid point size %s\n", argv[a]);
argError = true;
}
option = NONE;
break;
case JUSTIFY:
pIntEnd = NULL;
justification = strtol(argv[a], &pIntEnd, 10);
if (justification <= 0)
{
fprintf(stderr, "Justification value must be > 0 but was %s\n", argv[a]);
justification = 0;
}
option = NONE;
break;
case LINE_FILL:
pFloatEnd = NULL;
fTestSize = strtof(argv[a],&pFloatEnd);
// what is a good max width?
if (fTestSize > 0 && fTestSize < 10000)
{
width = fTestSize;
}
else
{
fprintf(stderr,"Invalid line width %s\n", argv[a]);
argError = true;
}
option = NONE;
break;
case FEAT:
features = argv[a];
option = NONE;
break;
case LOG:
closeLog();
log = fopen(argv[a], "w");
if (log == NULL)
{
fprintf(stderr,"Failed to open %s\n", argv[a]);
log = stdout;
}
option = NONE;
break;
case TRACE:
trace = argv[a];
option = NONE;
break;
case ALLTRACE:
alltrace = argv[a];
option = NONE;
break;
case SIZE :
pIntEnd = NULL;
codesize = strtol(argv[a],&pIntEnd, 10);
option = NONE;
break;
default:
option = NONE;
if (argv[a][0] == '-')
{
if (strcmp(argv[a], "-pt") == 0)
{
option = POINT_SIZE;
}
else if (strcmp(argv[a], "-dpi") == 0)
{
option = DPI;
}
else if (strcmp(argv[a], "-ls") == 0)
{
option = NONE;
lineStart = true;
}
else if (strcmp(argv[a], "-le") == 0)
{
option = NONE;
lineEnd = true;
}
else if (strcmp(argv[a], "-le") == 0)
{
option = NONE;
lineEnd = true;
}
else if (strcmp(argv[a], "-rtl") == 0)
{
option = NONE;
rtl = true;
}
else if (strcmp(argv[a], "-ws") == 0)
{
option = NONE;
ws = true;
}
else if (strcmp(argv[a], "-feat") == 0)
{
option = FEAT;
}
else if (strcmp(argv[a], "-bytes") == 0)
{
option = SIZE;
}
else if (strcmp(argv[a], "-noprint") == 0)
{
noprint = true;
}
else if (strcmp(argv[a], "-codes") == 0)
{
option = NONE;
useCodes = 4;
// must be less than argc
//pText32 = new unsigned int[argc];
pText32 = (unsigned int *)malloc(sizeof(unsigned int) * argc);
fprintf(log, "Text codes\n");
}
else if (strcmp(argv[a], "-auto") == 0)
{
const unsigned kCodeLimit = 0x1000;
charLength = kCodeLimit - 1;
pText32 = (unsigned int *)malloc(sizeof(unsigned int) * kCodeLimit);
unsigned int i;
for (i = 1; i < kCodeLimit; ++i)
pText32[i - 1] = i;
pText32[charLength] = 0;
autoCodes = true;
option = NONE;
}
else if (strcmp(argv[a], "-linefill") == 0)
{
option = LINE_FILL;
useLineFill = true;
}
else if (strcmp(argv[a], "-j") == 0)
{
option = JUSTIFY;
}
else if (strcmp(argv[a], "-log") == 0)
{
option = LOG;
}
else if (strcmp(argv[a], "-trace") == 0)
{
option = TRACE;
}
else if (strcmp(argv[a], "-alltrace") == 0)
{
option = ALLTRACE;
}
else if (strcmp(argv[a], "-demand") == 0)
{
option = NONE;
opts = gr_face_default;
}
else
{
argError = true;
fprintf(stderr,"Unknown option %s\n",argv[a]);
}
}
else if (mainArgOffset == 0)
{
fileName = argv[a];
mainArgOffset++;
}
else if (useCodes)
{
pIntEnd = NULL;
mainArgOffset++;
unsigned int code = strtol(argv[a],&pIntEnd, 16);
if (code > 0)
{
// convert text to utfOut using iconv because its easier to debug string placements
assert(pText32);
pText32[charLength++] = code;
if (charLength % 10 == 0)
fprintf(log, "%4x\n",code);
else
fprintf(log, "%4x\t",code);
}
else
{
fprintf(stderr,"Invalid dpi %s\n", argv[a]);
}
}
else if (mainArgOffset == 1)
{
mainArgOffset++;
pText = argv[a];
textArgIndex = a;
}
else
{
argError = true;
fprintf(stderr,"too many arguments %s\n",argv[a]);
}
break;
}
}
if (mainArgOffset < 1) argError = true;
else if (mainArgOffset > 1)
{
if (!autoCodes && !useCodes && pText != NULL)
{
charLength = convertUtf<gr2::utf8>(pText, pText32);
if (!pText32)
{
if (charLength == ~0)
perror("decoding utf-8 data failed");
else
perror("insufficent memory for text buffer");
}
fprintf(log, "String has %d characters\n", (int)charLength);
size_t ci;
for (ci = 0; ci < 10 && ci < charLength; ci++)
{
fprintf(log, "%d\t", (int)ci);
}
fprintf(log, "\n");
for (ci = 0; ci < charLength; ci++)
{
fprintf(log, "%04x\t", (int)ci);
if (((ci + 1) % 10) == 0)
fprintf(log, "\n");
}
fprintf(log, "\n");
}
else
{
assert(pText32);
pText32[charLength] = 0;
fprintf(log, "\n");
}
}
return (argError) ? false : true;
}
typedef uint32_t tag_t;
void Parameters::printFeatures(const gr_face * face) const
{
uint16_t numFeatures = gr_face_n_fref(face);
fprintf(log, "%d features\n", numFeatures);
uint16_t langId = 0x0409;
for (uint16_t i = 0; i < numFeatures; i++)
{
const gr_feature_ref * f = gr_face_fref(face, i);
uint32_t length = 0;
char * label = reinterpret_cast<char *>(gr_fref_label(f, &langId, gr_utf8, &length));
const tag_t featId = gr_fref_id(f);
if (label)
if ((char(featId >> 24) >= 0x20 && char(featId >> 24) < 0x7F) &&
(char(featId >> 16) >= 0x20 && char(featId >> 16) < 0x7F) &&
(char(featId >> 8) >= 0x20 && char(featId >> 8) < 0x7F) &&
(char(featId) >= 0x20 && char(featId) < 0x7F))
{
fprintf(log, "%d %c%c%c%c %s\n", featId, featId >> 24, featId >> 16, featId >> 8, featId, label);
}
else
{
fprintf(log, "%d %s\n", featId, label);
}
else
fprintf(log, "%d\n", featId);
gr_label_destroy(reinterpret_cast<void*>(label));
uint16_t numSettings = gr_fref_n_values(f);
for (uint16_t j = 0; j < numSettings; j++)
{
int16_t value = gr_fref_value(f, j);
label = reinterpret_cast<char *>(gr_fref_value_label
(f, j, &langId, gr_utf8, &length));
fprintf(log, "\t%d\t%s\n", value, label);
gr_label_destroy(reinterpret_cast<void*>(label));
}
}
uint16_t numLangs = gr_face_n_languages(face);
fprintf(log, "Feature Languages:");
for (uint16_t i = 0; i < numLangs; i++)
{
const tag_t lang_id = gr_face_lang_by_index(face, i);
fprintf(log, "\t");
for (size_t j = 4; j; --j)
{
const unsigned char c = lang_id >> (j*8-8);
if ((c >= 0x20) && (c < 0x80))
fprintf(log, "%c", c);
}
}
fprintf(log, "\n");
}
gr_feature_val * Parameters::parseFeatures(const gr_face * face) const
{
gr_feature_val * featureList = NULL;
const char * pLang = NULL;
tag_t lang_id = 0;
if (features && (pLang = strstr(features, "lang=")))
{
pLang += 5;
for (int i = 4; i; --i)
{
lang_id <<= 8;
if (!*pLang || *pLang == '0' || *pLang == '&') continue;
lang_id |= *pLang++;
}
}
featureList = gr_face_featureval_for_lang(face, lang_id);
if (!features || strlen(features) == 0)
return featureList;
size_t featureLength = strlen(features);
const char * name = features;
const char * valueText = NULL;
size_t nameLength = 0;
int32_t value = 0;
const gr_feature_ref* ref = NULL;
tag_t feat_id = 0;
for (size_t i = 0; i < featureLength; i++)
{
switch (features[i])
{
case ',':
case '&':
assert(valueText);
value = atoi(valueText);
if (ref)
{
gr_fref_set_feature_value(ref, value, featureList);
ref = NULL;
}
valueText = NULL;
name = features + i + 1;
nameLength = 0;
feat_id = 0;
break;
case '=':
if (nameLength <= 4)
{
ref = gr_face_find_fref(face, feat_id);
}
if (!ref)
{
assert(name);
feat_id = atoi(name);
ref = gr_face_find_fref(face, feat_id);
}
valueText = features + i + 1;
name = NULL;
break;
default:
if (valueText == NULL)
{
if (nameLength < 4)
feat_id = feat_id << 8 | features[i];
}
break;
}
if (ref)
{
assert(valueText);
value = atoi(valueText);
gr_fref_set_feature_value(ref, value, featureList);
if (feat_id > 0x20000000)
{
fprintf(log, "%c%c%c%c=%d\n", feat_id >> 24, feat_id >> 16, feat_id >> 8, feat_id, value);
}
else
fprintf(log, "%u=%d\n", feat_id, value);
ref = NULL;
}
}
return featureList;
}
int Parameters::testFileFont() const
{
int returnCode = 0;
// try
{
gr_face *face = NULL;
FILE * testfile = fopen(fileName, "rb");
if (!testfile)
{
fprintf(stderr, "Unable to open font file\n");
return 4;
}
else
fclose(testfile);
if (alltrace) gr_start_logging(NULL, alltrace);
face = gr_make_file_face(fileName, opts);
// use the -trace option to specify a file
if (trace) gr_start_logging(face, trace);
if (!face)
{
fprintf(stderr, "Invalid font, failed to read or parse tables\n");
return 3;
}
if (charLength == 0)
{
printFeatures(face);
gr_stop_logging(face);
gr_face_destroy(face);
return 0;
}
gr_font *sizedFont = gr_make_font(pointSize * dpi / 72, face);
gr_feature_val * featureList = NULL;
Gr2TextSrc textSrc(pText32, charLength);
gr_segment* pSeg = NULL;
if (features)
featureList = parseFeatures(face);
if (codesize == 2)
{
unsigned short *pText16 = (unsigned short *)malloc((textSrc.getLength() * 2 + 1) * sizeof(unsigned short));
gr2::utf16::iterator ui = pText16;
unsigned int *p = pText32;
for (unsigned int i = 0; i < textSrc.getLength(); ++i)
{
*ui = *p++;
ui++;
}
*ui = 0;
pSeg = gr_make_seg(sizedFont, face, 0, features ? featureList : NULL, (gr_encform)codesize, pText16, textSrc.getLength(), rtl ? 1 : 0);
}
else if (codesize == 1)
{
unsigned char *pText8 = (unsigned char *)malloc((textSrc.getLength() + 1) * 4);
gr2::utf8::iterator ui = pText8;
unsigned int *p = pText32;
for (unsigned int i = 0; i < textSrc.getLength(); ++i)
{
*ui = *p++;
ui++;
}
*ui = 0;
pSeg = gr_make_seg(sizedFont, face, 0, features ? featureList : NULL, (gr_encform)codesize, pText8, textSrc.getLength(), rtl ? 1 : 0);
free(pText8);
}
else
pSeg = gr_make_seg(sizedFont, face, 0, features ? featureList : NULL, textSrc.utfEncodingForm(),
textSrc.get_utf_buffer_begin(), textSrc.getLength(), rtl ? 1 : 0);
if (pSeg && !noprint)
{
int i = 0;
float advanceWidth;
#ifndef NDEBUG
int numSlots = gr_seg_n_slots(pSeg);
#endif
// size_t *map = new size_t [seg.length() + 1];
if (justification > 0)
advanceWidth = gr_seg_justify(pSeg, gr_seg_first_slot(pSeg), sizedFont, gr_seg_advance_X(pSeg) * justification / 100., gr_justCompleteLine, NULL, NULL);
else
advanceWidth = gr_seg_advance_X(pSeg);
size_t *map = (size_t*)malloc((gr_seg_n_slots(pSeg) + 1) * sizeof(size_t));
for (const gr_slot* slot = gr_seg_first_slot(pSeg); slot; slot = gr_slot_next_in_segment(slot), ++i)
{ map[i] = (size_t)slot; }
map[i] = 0;
fprintf(log, "Segment length: %d\n", gr_seg_n_slots(pSeg));
fprintf(log, "pos gid attach\t x\t y\tins bw\t chars\t\tUnicode\t");
fprintf(log, "\n");
i = 0;
for (const gr_slot* slot = gr_seg_first_slot(pSeg); slot; slot = gr_slot_next_in_segment(slot), ++i)
{
// consistency check for last slot
assert((i + 1 < numSlots) || (slot == gr_seg_last_slot(pSeg)));
float orgX = gr_slot_origin_X(slot);
float orgY = gr_slot_origin_Y(slot);
const gr_char_info *cinfo = gr_seg_cinfo(pSeg, gr_slot_original(slot));
fprintf(log, "%02d %4d %3d@%d,%d\t%6.1f\t%6.1f\t%2d%4d\t%3d %3d\t",
i, gr_slot_gid(slot), lookup(map, (size_t)gr_slot_attached_to(slot)),
gr_slot_attr(slot, pSeg, gr_slatAttX, 0),
gr_slot_attr(slot, pSeg, gr_slatAttY, 0), orgX, orgY, gr_slot_can_insert_before(slot) ? 1 : 0,
cinfo ? gr_cinfo_break_weight(cinfo) : 0, gr_slot_before(slot), gr_slot_after(slot));
if (pText32 != NULL && gr_slot_before(slot) + offset < charLength
&& gr_slot_after(slot) + offset < charLength)
{
fprintf(log, "%7x\t%7x",
pText32[gr_slot_before(slot) + offset],
pText32[gr_slot_after(slot) + offset]);
}
fprintf(log, "\n");
}
assert(i == numSlots);
// assign last point to specify advance of the whole array
// position arrays must be one bigger than what countGlyphs() returned
fprintf(log, "Advance width = %6.1f\n", advanceWidth);
unsigned int numchar = gr_seg_n_cinfo(pSeg);
fprintf(log, "\nChar\tUnicode\tBefore\tAfter\tBase\n");
for (unsigned int j = 0; j < numchar; j++)
{
const gr_char_info *c = gr_seg_cinfo(pSeg, j);
fprintf(log, "%d\t%04X\t%d\t%d\t%d\n", j, gr_cinfo_unicode_char(c), gr_cinfo_before(c), gr_cinfo_after(c), int(gr_cinfo_base(c)));
}
free(map);
}
if (pSeg)
gr_seg_destroy(pSeg);
if (featureList) gr_featureval_destroy(featureList);
gr_font_destroy(sizedFont);
if (trace) gr_stop_logging(face);
gr_face_destroy(face);
if (alltrace) gr_stop_logging(NULL);
}
return returnCode;
}
int lookup(size_t *map, size_t val)
{
int i = 0;
for ( ; map[i] != val && map[i]; i++) {}
return map[i] ? i : -1;
}
int main(int argc, char *argv[])
{
Parameters parameters;
if (!parameters.loadFromArgs(argc, argv))
{
fprintf(stderr,"Usage: %s [options] fontfile utf8text \n",argv[0]);
fprintf(stderr,"Options: (default in brackets)\n");
fprintf(stderr,"-dpi d\tDots per Inch (72)\n");
fprintf(stderr,"-pt d\tPoint size (12)\n");
fprintf(stderr,"-codes\tEnter text as hex code points instead of utf8 (false)\n");
fprintf(stderr,"\te.g. %s font.ttf -codes 1000 102f\n",argv[0]);
fprintf(stderr,"-auto\tAutomatically generate a test string of all codes 1-0xFFF\n");
fprintf(stderr,"-noprint\tDon't print results\n");
//fprintf(stderr,"-ls\tStart of line = true (false)\n");
//fprintf(stderr,"-le\tEnd of line = true (false)\n");
fprintf(stderr,"-rtl\tRight to left = true (false)\n");
fprintf(stderr,"-j percentage\tJustify to percentage of string width\n");
//fprintf(stderr,"-ws\tAllow trailing whitespace = true (false)\n");
//fprintf(stderr,"-linefill w\tuse a LineFillSegment of width w (RangeSegment)\n");
fprintf(stderr,"\nIf a font, but no text is specified, then a list of features will be shown.\n");
fprintf(stderr,"-feat f=g\tSet feature f to value g. Separate multiple features with ,\n");
fprintf(stderr,"-log out.log\tSet log file to use rather than stdout\n");
fprintf(stderr,"-trace trace.json\tDefine a file for the JSON trace log\n");
fprintf(stderr,"-demand\tDemand load glyphs and cmap cache\n");
fprintf(stderr,"-bytes\tword size for character transfer [1,2,4] defaults to 4\n");
return 1;
}
return parameters.testFileFont();
}