First Commit
This commit is contained in:
commit
ae0fab9400
424 files changed
+71006
No files matched your search
@@ -0,0 +1,190 @@
|
||||
_UI_ENUM(uiAttribute) {
|
||||
uiAttributeFamily,
|
||||
uiAttributeSize, // use Double
|
||||
uiAttributeWeight,
|
||||
uiAttributeItalic,
|
||||
uiAttributeStretch,
|
||||
uiAttributeColor, // use R, G, B, A
|
||||
uiAttributeBackground, // use R, G, B, A
|
||||
|
||||
// TODO kerning amount
|
||||
// OS X: kCTKernAttributeName
|
||||
// > 0: farther (TODO from advance or standard kerning?)
|
||||
// == 0: no kerning
|
||||
// < 0: closer (TODO same)
|
||||
// undefined: standard kerning
|
||||
// Pango: pango_attr_letter_spacing_new()
|
||||
// parameter meaning unspecified
|
||||
// Windows: requires Platform Update, SetLetterSpacing()
|
||||
// parameter meaning unspecified
|
||||
|
||||
uiAttributeUnderline, // enum uiDrawUnderlineStyle
|
||||
// TODO what is the color in the case we don't specify it, black or the text color?
|
||||
uiAttributeUnderlineColor, // enum uiDrawUnderlineColor
|
||||
|
||||
// TODO kCTSuperscriptAttributeName vs below
|
||||
// all it does is set the below attribute so
|
||||
|
||||
// TODO kCTBaselineClassAttributeName, kCTBaselineInfoAttributeName, kCTBaselineReferenceInfoAttributeName
|
||||
|
||||
// TODO strikethroughs? (pango yes, directwrite yes, os x no)
|
||||
// TODO baseline offsets? (pango yes)
|
||||
// TODO size scales? (pango yes)
|
||||
// TODO fallbacks (pango: enable or disable)
|
||||
|
||||
// TODO document that this will also enable language-specific font features (TODO on DirectWrite too?)
|
||||
// TODO document that this should be strict BCP 47 form (A-Z, a-z, 0-9, and -) for maximum compatibility
|
||||
uiAttributeLanguage, // BCP 47 string
|
||||
|
||||
// These attributes represent typographic features. Each feature
|
||||
// is a separate attribute, to make composition easier. The
|
||||
// availability of for each attribute are defined by the font; the
|
||||
// default values are defined by the font and/or by the OS.
|
||||
//
|
||||
// A note about features whose parameter is an enumeration:
|
||||
// OS X defines typographic features using the AAT specification
|
||||
// and converts to OpenType internally when needed, whereas
|
||||
// other platforms use OpenType directly. OpenType is less
|
||||
// precise about what each enumeration value means than AAT
|
||||
// is, so enumeration values do not necessarily represent what
|
||||
// OS X expects with all fonts. In cases where they do, libui
|
||||
// provides an enumeration type to use. Otherwise, the AAT
|
||||
// enumeration values are provided in comments for
|
||||
// documentation purposes.
|
||||
|
||||
// TODO kAllTypographicFeaturesType
|
||||
|
||||
// AAT calls these "common ligatures"
|
||||
uiAttributeStandardLigatures, // 0 = off, 1 = on
|
||||
uiAttributeRequiredLigatures, // 0 = off, 1 = on
|
||||
// AAT calls these "rare ligatures"
|
||||
uiAttributeDiscretionaryLigatures, // 0 = off, 1 = on
|
||||
uiAttributeContextualLigatures, // 0 = off, 1 = on
|
||||
uiAttributeHistoricalLigatures, // 0 = off, 1 = on
|
||||
|
||||
// TODO uiAttributeCursiveConnection, // 0 = none, 1 = some, 2 = all
|
||||
|
||||
uiAttributeUnicase, // 0 = off, 1 = on
|
||||
|
||||
// TODO uiAttributeLinguisticRearrangement, // 0 = off, 1 = on
|
||||
|
||||
// TODO rename this
|
||||
uiAttributeNumberSpacings, // enum uiAttributeNumberSpacing
|
||||
|
||||
// TODO kSmartSwashType, falt and jalt
|
||||
|
||||
// TODO kDiacriticsType
|
||||
|
||||
uiAttributeSuperscripts, // enum uiAttributeSuperscript
|
||||
|
||||
uiAttributeFractionForms, // enum uiAttributeFractionForm
|
||||
|
||||
uiAttributeSlashedZero, // 0 = off, 1 = on
|
||||
|
||||
uiAttributeMathematicalGreek, // 0 = off, 1 = on
|
||||
|
||||
// AAT defines the following values:
|
||||
// 0 = none
|
||||
// 1 = dingbats
|
||||
// 2 = pi characters
|
||||
// 3 = fleurons
|
||||
// 4 = decorative borders
|
||||
// 5 = international symbols
|
||||
// 6 = mathematical symbols
|
||||
// OpenType says alphanumeric characters must(? TODO) have one form each and the bullet character U+2022 (•) can have many
|
||||
uiAttributeOrnamentalForms, // an integer from 0 to a font-specified upper bound
|
||||
// TODO provide a function to get the upper bound?
|
||||
|
||||
// AAT calls this "character alternatives" and defines the
|
||||
// following values:
|
||||
// 0 = none
|
||||
// OpenType calls this "access all alternates".
|
||||
// TODO doesn't OpenType do the same about 0?
|
||||
uiAttributeSpecificCharacterForm, // an integer from 0 to a font-specified upper bound
|
||||
// TODO provide a function to get the upper bound?
|
||||
|
||||
uiAttributeTitlingCapitalForms, // 0 = off, 1 = on
|
||||
|
||||
// AAT calls these "character shapes"
|
||||
uiAttributeHanCharacterForms, // enum uiAttributeHanCharacterForm
|
||||
|
||||
// OpenType calls these "old-style"
|
||||
uiAttributeLowercaseNumbers, // 0 = off, 1 = on
|
||||
|
||||
// TODO kTextSpacingType
|
||||
// see kKanaSpacingType below
|
||||
|
||||
uiAttributeHanjaToHangul, // 0 = off, 1 = on
|
||||
|
||||
// AAT defines the following values:
|
||||
// 0 = none
|
||||
// 1 = box
|
||||
// 2 = rounded box
|
||||
// 3 = circle
|
||||
// 4 = inverted circle
|
||||
// 5 = parentheses
|
||||
// 6 = period
|
||||
// 7 = roman numeral
|
||||
// 8 = diamond
|
||||
// 9 = inverted box
|
||||
// 10 = inverted rounded box
|
||||
// TODO rename to AnnotatedForms?
|
||||
uiAttributeAnnotatedGlyphForms, // an integer from 0 to a font-specified upper bound
|
||||
// TODO provide a function to get the upper bound?
|
||||
|
||||
// TODO kKanaSpacingType
|
||||
// TODO kIdeographicSpacingType
|
||||
// can they be provided independently of kTextSpacingType? Core Text doesn't seem to
|
||||
|
||||
// TODO kUnicodeDecompositionType
|
||||
|
||||
uiAttributeRubyKanaForms, // 0 = off, 1 = on
|
||||
|
||||
// TODO kCJKVerticalRomanPlacementType
|
||||
// this is 'valt' in OpenType but I don't know if I want to make it selectable or not
|
||||
|
||||
uiAttributeCJKRomansToItalics, // 0 = off, 1 = on
|
||||
|
||||
// AAT calls this "case-sensitive layout"
|
||||
uiAttributeCaseSensitiveForms, // 0 = off, 1 = on
|
||||
// AAT: this is called "case-sensitive spacing"
|
||||
uiAttributeCapitalSpacing, // 0 = off, 1 = on
|
||||
|
||||
uiAttributeAlternateHorizontalKana, // 0 = off, 1 = on
|
||||
uiAttributeAlternateVerticalKana, // 0 = off, 1 = on
|
||||
|
||||
// TODO "Alternate"? unify all this
|
||||
// TODO document that these are guaranteed to be consecutive
|
||||
uiAttributeStylisticAlternate1, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate2, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate3, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate4, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate5, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate6, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate7, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate8, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate9, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate10, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate11, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate12, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate13, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate14, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate15, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate16, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate17, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate18, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate19, // 0 = off, 1 = on
|
||||
uiAttributeStylisticAlternate20, // 0 = off, 1 = on
|
||||
|
||||
uiAttributeContextualAlternates, // 0 = off, 1 = on
|
||||
uiAttributeSwashes, // 0 = off, 1 = on
|
||||
uiAttributeContextualSwashes, // 0 = off, 1 = on
|
||||
|
||||
uiAttributeLowercaseCapForms, // enum uiAttributeCapForm
|
||||
uiAttributeUppercaseCapForms, // enum uiAttributeCapForm
|
||||
|
||||
// TODO kCJKRomanSpacingType
|
||||
|
||||
// TODO uiAttributeSystem, (this might not be doable with DirectWrite)
|
||||
// TODO uiAttributeCustom,
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
Removed because proper support on OS X doesn't come until 10.9 unless we use a font with an ltag table; none of the fonts I have come with ltag tables (none of the fonts on OS X do, or at least don't come with a sr entry in their ltag table, and OpenType has replaced ltag with what appears to be custom sub-tables of the GPOS and GSUB tables.)
|
||||
@@ -0,0 +1,19 @@
|
||||
struct fontParams {
|
||||
uiDrawFontDescriptor desc;
|
||||
uint16_t featureTypes[maxFeatures];
|
||||
uint16_t featureSelectors[maxFeatures];
|
||||
size_t nFeatures;
|
||||
const char *language;
|
||||
};
|
||||
|
||||
|
||||
// locale identifiers are specified as BCP 47: https://developer.apple.com/reference/corefoundation/cflocale?language=objc
|
||||
case uiAttributeLanguage:
|
||||
// LONGTERM FUTURE when we move to 10.9, switch to using kCTLanguageAttributeName
|
||||
ensureFontInRange(p, start, end);
|
||||
adjustFontInRange(p, start, end, ^(struct fontParams *fp) {
|
||||
fp->language = (const char *) (spec->Value);
|
||||
});
|
||||
break;
|
||||
|
||||
desc = fontdescAppendFeatures(desc, fp->featureTypes, fp->featureSelectors, fp->nFeatures, fp->language);
|
||||
@@ -0,0 +1,9 @@
|
||||
PangoLanguage *lang;
|
||||
|
||||
// language strings are specified as BCP 47: https://developer.gnome.org/pango/1.30/pango-Scripts-and-Languages.html#pango-language-from-string https://www.ietf.org/rfc/rfc3066.txt
|
||||
case uiAttributeLanguage:
|
||||
lang = pango_language_from_string((const char *) (spec->Value));
|
||||
addattr(p, start, end,
|
||||
pango_attr_language_new(lang));
|
||||
// lang *cannot* be freed
|
||||
break;
|
||||
@@ -0,0 +1,10 @@
|
||||
WCHAR *localeName;
|
||||
|
||||
// locale names are specified as BCP 47: https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx https://www.ietf.org/rfc/rfc4646.txt
|
||||
case uiAttributeLanguage:
|
||||
localeName = toUTF16((char *) (spec->Value));
|
||||
hr = p->layout->SetLocaleName(localeName, range);
|
||||
if (hr != S_OK)
|
||||
logHRESULT(L"error applying locale name attribute", hr);
|
||||
uiFree(localeName);
|
||||
break;
|
||||
@@ -0,0 +1,2 @@
|
||||
case uiAttributeLanguage:
|
||||
return asciiStringsEqualCaseFold((char *) (attr->spec.Value), (char *) (spec->Value));
|
||||
@@ -0,0 +1,27 @@
|
||||
before "or any combination of the above"
|
||||
|
||||
// thanks to https://twitter.com/codeman38/status/831924064012886017
|
||||
next = "\xD0\xB1\xD0\xB3\xD0\xB4\xD0\xBF\xD1\x82";
|
||||
uiAttributedStringAppendUnattributed(attrstr, "multiple languages (compare ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeItalic;
|
||||
spec.Value = uiDrawTextItalicItalic;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
spec.Type = uiAttributeLanguage;
|
||||
spec.Value = (uintptr_t) "ru";
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " to ");
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeItalic;
|
||||
spec.Value = uiDrawTextItalicItalic;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
spec.Type = uiAttributeLanguage;
|
||||
spec.Value = (uintptr_t) "sr";
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " \xE2\x80\x94 may require changing the font)");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
@@ -0,0 +1,112 @@
|
||||
// note: this doesn't work for languages; we have to parse the ltag table
|
||||
|
||||
// fortunately features that aren't supported are simply ignored, so we can copy them all in
|
||||
// LONGTERM FUTURE when we switch to 10.9, the language parameter won't be needed anymore
|
||||
// LONGTERM FUTURE and on 10.10 we can use OpenType tags directly!
|
||||
CTFontDescriptorRef fontdescAppendFeatures(CTFontDescriptorRef desc, const uint16_t *types, const uint16_t *selectors, size_t n, const char *language)
|
||||
{
|
||||
CTFontDescriptorRef new;
|
||||
CFMutableArrayRef outerArray;
|
||||
CFDictionaryRef innerDict;
|
||||
CFNumberRef numType, numSelector;
|
||||
const void *keys[2], *values[2];
|
||||
size_t i;
|
||||
CFArrayRef languages;
|
||||
CFIndex il, nl;
|
||||
CFStringRef curlang;
|
||||
char d[2];
|
||||
|
||||
outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
||||
if (outerArray == NULL) {
|
||||
// TODO
|
||||
}
|
||||
keys[0] = kCTFontFeatureTypeIdentifierKey;
|
||||
keys[1] = kCTFontFeatureSelectorIdentifierKey;
|
||||
for (i = 0; i < n; i++) {
|
||||
numType = CFNumberCreate(NULL, kCFNumberSInt16Type,
|
||||
(const SInt16 *) (types + i));
|
||||
numSelector = CFNumberCreate(NULL, kCFNumberSInt16Type,
|
||||
(const SInt16 *) (selectors + i));
|
||||
values[0] = numType;
|
||||
values[1] = numSelector;
|
||||
innerDict = CFDictionaryCreate(NULL,
|
||||
keys, values, 2,
|
||||
// TODO are these correct?
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
if (innerDict == NULL) {
|
||||
// TODO
|
||||
}
|
||||
CFArrayAppendValue(outerArray, innerDict);
|
||||
CFRelease(innerDict);
|
||||
CFRelease(numSelector);
|
||||
CFRelease(numType);
|
||||
}
|
||||
|
||||
// now we have to take care of the language
|
||||
if (language != NULL) {
|
||||
languages = CTFontDescriptorCopyAttribute(desc, kCTFontLanguagesAttribute);
|
||||
if (languages != NULL) {
|
||||
nl = CFArrayGetCount(languages);
|
||||
d[0] = language[0];
|
||||
if (d[0] >= 'A' && d[0] <= 'Z')
|
||||
d[0] += 'a' - 'A';
|
||||
d[1] = language[1];
|
||||
if (d[1] >= 'A' && d[1] <= 'Z')
|
||||
d[1] += 'a' - 'A';
|
||||
for (il = 0; il < nl; il++) {
|
||||
char c[2];
|
||||
|
||||
curlang = (CFStringRef) CFArrayGetValueAtIndex(languages, il);
|
||||
// TODO check for failure
|
||||
CFStringGetBytes(curlang, CFRangeMake(0, 2),
|
||||
kCFStringEncodingUTF8, 0, false,
|
||||
(UInt8 *) c, 2, NULL);
|
||||
if (c[0] >= 'A' && c[0] <= 'Z')
|
||||
c[0] += 'a' - 'A';
|
||||
if (c[1] >= 'A' && c[1] <= 'Z')
|
||||
c[1] += 'a' - 'A';
|
||||
if (c[0] == d[0] && c[1] == d[1])
|
||||
break;
|
||||
}
|
||||
if (il != nl) {
|
||||
uint16_t typ;
|
||||
|
||||
typ = kLanguageTagType;
|
||||
il++;
|
||||
numType = CFNumberCreate(NULL, kCFNumberSInt16Type,
|
||||
(const SInt16 *) (&typ));
|
||||
numSelector = CFNumberCreate(NULL, kCFNumberCFIndexType,
|
||||
&il);
|
||||
values[0] = numType;
|
||||
values[1] = numSelector;
|
||||
innerDict = CFDictionaryCreate(NULL,
|
||||
keys, values, 2,
|
||||
// TODO are these correct?
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
if (innerDict == NULL) {
|
||||
// TODO
|
||||
}
|
||||
CFArrayAppendValue(outerArray, innerDict);
|
||||
CFRelease(innerDict);
|
||||
CFRelease(numSelector);
|
||||
CFRelease(numType);
|
||||
}
|
||||
CFRelease(languages);
|
||||
}
|
||||
}
|
||||
|
||||
keys[0] = kCTFontFeatureSettingsAttribute;
|
||||
values[0] = outerArray;
|
||||
innerDict = CFDictionaryCreate(NULL,
|
||||
keys, values, 1,
|
||||
// TODO are these correct?
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
CFRelease(outerArray);
|
||||
new = CTFontDescriptorCreateCopyWithAttributes(desc, innerDict);
|
||||
CFRelease(desc);
|
||||
CFRelease(innerDict);
|
||||
return new;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
after UnderlineColor, before feature tags
|
||||
|
||||
// TODO document that this will also enable language-specific font features (TODO on DirectWrite too?)
|
||||
// TODO document that this should be strict BCP 47 form (A-Z, a-z, 0-9, and -) for maximum compatibility
|
||||
uiAttributeLanguage, // BCP 47 string
|
||||
@@ -0,0 +1,25 @@
|
||||
= attributed strings
|
||||
attribute lengths are rounded to complete unicode codepoints
|
||||
zero-length attributes are elided
|
||||
consecutive attributes of the same type and value are merged
|
||||
overlapping attributes of different types do not split each other
|
||||
overlapping attributes of the same type but different values do split
|
||||
empty string is allowed
|
||||
empty string cannot have attributes
|
||||
font family names are case-insensitive both in attributes and in descriptors
|
||||
attributes are unique throughout a Unicode codepoint, not just to UTF-8 bytes
|
||||
define what "it is an error" means in the case of uiFreeAttribute() and all uiAttributeValue() functions and constructors
|
||||
does uiAttributeFamily() return a normalized string
|
||||
should uiNewAttributeBackground() be renamed to uiNewAttributeBackgroundColor() and likewise for the type constant
|
||||
should underline colors just ignore non-custom component arguments
|
||||
should any color getter function accept a NULL pointer
|
||||
what should uiAttributeUnderlineColor() do if the color type isn't Custom but the other pointers are non-NULL
|
||||
should uiOpenTypeFeaturesGet() accept a NULL value pointer
|
||||
what happens if uiOpenTypeFeaturesForEach() is given a NULl function pointer
|
||||
should FeaturesAttribute be changed to OpenTypeFeaturesAttribute and likewise for the type enum
|
||||
should uiNewFeaturesAttribute() accept NULL
|
||||
should uiNewFamilyAttribute() accept NULL
|
||||
it is an error in ForEach too
|
||||
invalid values for uiDrawTextAlign
|
||||
empty text layouts have one line
|
||||
TODO figure out what to do if any field (particularly the font family name) in uiFontDescriptor is unset
|
||||
@@ -0,0 +1,115 @@
|
||||
// 27 february 2018
|
||||
#ifndef TODO_TEST
|
||||
#error TODO this is where libui itself goes
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
#include "testing.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
typedef struct uiOpenTypeFeatures uiOpenTypeFeatures;
|
||||
typedef int uiForEach;
|
||||
enum { uiForEachContinue, uiForEachStop };
|
||||
typedef uiForEach (*uiOpenTypeFeaturesForEachFunc)(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data);
|
||||
#define uiprivNew(x) ((x *) malloc(sizeof (x)))
|
||||
#define uiprivAlloc(x,y) malloc(x)
|
||||
#define uiprivRealloc(x,y,z) realloc(x,y)
|
||||
#define uiprivFree free
|
||||
#include "opentype.c"
|
||||
|
||||
static void freeOpenType(void *otf)
|
||||
{
|
||||
uiFreeOpenTypeFeatures((uiOpenTypeFeatures *) otf);
|
||||
}
|
||||
|
||||
testingTest(OpenTypeFeaturesAddGet)
|
||||
{
|
||||
uiOpenTypeFeatures *otf;
|
||||
int got;
|
||||
uint32_t value;
|
||||
|
||||
otf = uiNewOpenTypeFeatures();
|
||||
testingTDefer(t, freeOpenType, otf);
|
||||
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
|
||||
got = uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value);
|
||||
|
||||
if (!got)
|
||||
testingTErrorf(t, "uiOpenTypeFeaturesGet() failed to get feature we added");
|
||||
else if (value != 12345)
|
||||
testingTErrorf(t, "feature abcd: got %" PRIu32 ", want 12345", value);
|
||||
}
|
||||
|
||||
testingTest(OpenTypeFeaturesRemove)
|
||||
{
|
||||
uiOpenTypeFeatures *otf;
|
||||
uint32_t value;
|
||||
|
||||
otf = uiNewOpenTypeFeatures();
|
||||
testingTDefer(t, freeOpenType, otf);
|
||||
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
|
||||
uiOpenTypeFeaturesRemove(otf, 'a', 'b', 'c', 'd');
|
||||
|
||||
if (uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value))
|
||||
testingTErrorf(t, "uiOpenTypeFeaturesGet() succeeded in getting deleted feature; value %" PRIu32, value);
|
||||
}
|
||||
|
||||
testingTest(OpenTypeFeaturesCloneAdd)
|
||||
{
|
||||
uiOpenTypeFeatures *otf, *otf2;
|
||||
uint32_t value;
|
||||
|
||||
otf = uiNewOpenTypeFeatures();
|
||||
testingTDefer(t, freeOpenType, otf);
|
||||
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
|
||||
otf2 = uiOpenTypeFeaturesClone(otf);
|
||||
testingTDefer(t, freeOpenType, otf2);
|
||||
uiOpenTypeFeaturesAdd(otf2, 'q', 'w', 'e', 'r', 56789);
|
||||
|
||||
if (uiOpenTypeFeaturesGet(otf, 'q', 'w', 'e', 'r', &value))
|
||||
testingTErrorf(t, "uiOpenTypeFeaturesGet() on original succeeded in getting feature added to clone; value %" PRIu32, value);
|
||||
}
|
||||
|
||||
testingTest(OpenTypeFeaturesCloneModify)
|
||||
{
|
||||
uiOpenTypeFeatures *otf, *otf2;
|
||||
uint32_t value;
|
||||
|
||||
otf = uiNewOpenTypeFeatures();
|
||||
testingTDefer(t, freeOpenType, otf);
|
||||
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
|
||||
otf2 = uiOpenTypeFeaturesClone(otf);
|
||||
testingTDefer(t, freeOpenType, otf2);
|
||||
uiOpenTypeFeaturesAdd(otf2, 'a', 'b', 'c', 'd', 56789);
|
||||
|
||||
uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value);
|
||||
if (value != 12345)
|
||||
testingTErrorf(t, "uiOpenTypeFeaturesGet() on original: got %" PRIu32 ", want 12345", value);
|
||||
uiOpenTypeFeaturesGet(otf2, 'a', 'b', 'c', 'd', &value);
|
||||
if (value != 56789)
|
||||
testingTErrorf(t, "uiOpenTypeFeaturesGet() on clone: got %" PRIu32 ", want 56789", value);
|
||||
}
|
||||
|
||||
testingTest(OpenTypeFeaturesCloneRemove)
|
||||
{
|
||||
uiOpenTypeFeatures *otf, *otf2;
|
||||
uint32_t value;
|
||||
|
||||
otf = uiNewOpenTypeFeatures();
|
||||
testingTDefer(t, freeOpenType, otf);
|
||||
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
|
||||
otf2 = uiOpenTypeFeaturesClone(otf);
|
||||
testingTDefer(t, freeOpenType, otf2);
|
||||
uiOpenTypeFeaturesRemove(otf2, 'a', 'b', 'c', 'd');
|
||||
|
||||
if (uiOpenTypeFeaturesGet(otf2, 'a', 'b', 'c', 'd', &value))
|
||||
testingTErrorf(t, "uiOpenTypeFeaturesGet() on clone succeeded in getting feature removed from clone; value %" PRIu32, value);
|
||||
if (!uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value))
|
||||
testingTErrorf(t, "uiOpenTypeFeaturesGet() on original failed to get feature removed from clone");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
return testingMain();
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// 27 february 2018
|
||||
|
||||
// TODO
|
||||
// - https://blogs.msdn.microsoft.com/oldnewthing/20181107-00/?p=100155 https://blogs.msdn.microsoft.com/oldnewthing/20181108-00/?p=100165 https://blogs.msdn.microsoft.com/oldnewthing/20181109-00/?p=100175
|
||||
// - also in the above: note the unspecified order of data in the sub-segments...
|
||||
|
||||
#ifndef testingprivIncludeGuard_testing_h
|
||||
#define testingprivIncludeGuard_testing_h
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#undef testingprivBadLanguageVersion
|
||||
#ifdef __cplusplus
|
||||
// TODO https://stackoverflow.com/questions/2324658/how-to-determine-the-version-of-the-c-standard-used-by-the-compiler implies this won't do with C++0x-era compilers, and https://wiki.apache.org/stdcxx/C++0xCompilerSupport doesn't talk about va_copy() so a simple version check for the C99 preprocessor may be wrong...
|
||||
// TODO what if __cplusplus is blank (maybe only in that case, since IIRC C++98 requires __cplusplus to have a value)?
|
||||
#if __cplusplus < 201103L
|
||||
#define testingprivBadLanguageVersion
|
||||
#endif
|
||||
#elif !defined(__STDC_VERSION__)
|
||||
#define testingprivBadLanguageVersion
|
||||
#elif __STDC_VERSION__ < 199901L
|
||||
#define testingprivBadLanguageVersion
|
||||
#endif
|
||||
#ifdef testingprivBadLanguageVersion
|
||||
#error sorry, TODO requires either C99 or C++11; cannot continue
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define testingprivMkScaffold(name) \
|
||||
static inline void testingprivScaffold ## name(testingT *t) \
|
||||
{ \
|
||||
bool failedNow = false, skippedNow = false; \
|
||||
try { name(t); } \
|
||||
catch (testingprivFailNowException e) { failedNow = true; } \
|
||||
catch (testingprivSkipNowException e) { skippedNow = true; } \
|
||||
/* TODO see if we should catch other exceptions too */ \
|
||||
/* don't call these in the catch blocks as they call longjmp() */ \
|
||||
if (failedNow) testingprivTDoFailNow(t); \
|
||||
if (skippedNow) testingprivTDoSkipNow(t); \
|
||||
}
|
||||
#else
|
||||
#define testingprivMkScaffold(name) \
|
||||
static inline void testingprivScaffold ## name(testingT *t) { name(t); }
|
||||
#endif
|
||||
|
||||
// references:
|
||||
// - https://gitlab.gnome.org/GNOME/glib/blob/master/glib/gconstructor.h
|
||||
// - https://gitlab.gnome.org/GNOME/glib/blob/master/gio/glib-compile-resources.c
|
||||
// - https://msdn.microsoft.com/en-us/library/bb918180.aspx
|
||||
#if defined(__cplusplus)
|
||||
#define testingprivMkCtor(name, reg) \
|
||||
static reg ## Class testingprivCtor ## name(#name, testingprivScaffold ## name);
|
||||
#elif defined(__GNUC__)
|
||||
#define testingprivMkCtor(name, reg) \
|
||||
__attribute__((constructor)) static void testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); }
|
||||
#elif defined(_MSC_VER)
|
||||
#define testingprivMkCtorPrototype(name, reg) \
|
||||
static int name(void) testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); return 0; } \
|
||||
__pragma(section(".CRT$XCU",read)) \
|
||||
__declspec(allocate(".CRT$XCU")) static int (*testingprivCtorPtr ## name)(void) = testingprivCtor ## name;
|
||||
#elif defined(__SUNPRO_C)
|
||||
#define testingprivMkCtor(name, reg) \
|
||||
_Pragma("init(testingprivCtor" #name ")") static void testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); }
|
||||
#else
|
||||
#error unknown compiler for making constructors in C; cannot continue
|
||||
#endif
|
||||
|
||||
#define testingTest(Name) \
|
||||
void Test ## Name(testingT *t); \
|
||||
testingprivMkScaffold(Test ## Name) \
|
||||
testingprivMkCtor(Test ## Name, testingprivRegisterTest) \
|
||||
void Test ## Name(testingT *t)
|
||||
|
||||
extern int testingMain(void);
|
||||
|
||||
typedef struct testingT testingT;
|
||||
#define testingTLogf(t, ...) \
|
||||
testingprivExpand(testingprivTLogfThen((void), t, __VA_ARGS__))
|
||||
#define testingTLogvf(t, format, ap) \
|
||||
testingprivTLogvfThen((void), t, format, ap)
|
||||
#define testingTErrorf(t, ...) \
|
||||
testingprivExpand(testingprivTLogfThen(testingTFail, t, __VA_ARGS__))
|
||||
#define testingTErrorvf(t, format, ap) \
|
||||
testingprivTLogvfThen(testingTFail, t, format, ap)
|
||||
#define testingTFatalf(t, ...) \
|
||||
testingprivExpand(testingprivTLogfThen(testingTFailNow, t, __VA_ARGS__))
|
||||
#define testingTFatalvf(t, format, ap) \
|
||||
testingprivTLogvfThen(testingTFailNow, t, format, ap)
|
||||
#define testingTSkipf(t, ...) \
|
||||
testingprivExpand(testingprivTLogfThen(testingTSkipNow, t, __VA_ARGS__))
|
||||
#define testingTSkipvf(t, format, ap) \
|
||||
testingprivTLogvfThen(testingTSkipNow, t, format, ap)
|
||||
extern void testingTFail(testingT *t);
|
||||
#ifdef __cplusplus
|
||||
#define testingTFailNow(t) (throw testingprivFailNowException())
|
||||
#define testingTSkipNow(t) (throw testingprivSkipNowException())
|
||||
#else
|
||||
#define testingTFailNow(t) (testingprivTDoFailNow(t))
|
||||
#define testingTSkipNow(t) (testingprivTDoSkipNow(t))
|
||||
#endif
|
||||
// TODO should the defered function also have t passed to it?
|
||||
extern void testingTDefer(testingT *t, void (*f)(void *data), void *data);
|
||||
|
||||
// TODO IEEE 754 helpers
|
||||
// references:
|
||||
// - https://www.sourceware.org/ml/libc-alpha/2009-04/msg00005.html
|
||||
// - https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
|
||||
// - https://stackoverflow.com/questions/5085533/is-a-c-preprocessor-identical-to-a-c-preprocessor
|
||||
|
||||
// TODO should __LINE__ arguments use intmax_t or uintmax_t instead of int?
|
||||
extern void testingprivRegisterTest(const char *, void (*)(testingT *));
|
||||
// see https://stackoverflow.com/questions/32399191/va-args-expansion-using-msvc
|
||||
#define testingprivExpand(x) x
|
||||
#define testingprivTLogfThen(then, t, ...) ((testingprivTLogfFull(t, __FILE__, __LINE__, __VA_ARGS__)), (then(t)))
|
||||
#define testingprivTLogvfThen(then, t, format, ap) ((testingprivTLogvfFull(t, __FILE__, __LINE__, format, ap)), (then(t)))
|
||||
extern void testingprivTLogfFull(testingT *, const char *, int, const char *, ...);
|
||||
extern void testingprivTLogvfFull(testingT *, const char *, int, const char *, va_list);
|
||||
extern void testingprivTDoFailNow(testingT *);
|
||||
extern void testingprivTDoSkipNow(testingT *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
namespace {
|
||||
class testingprivFailNowException {};
|
||||
class testingprivSkipNowException {};
|
||||
class testingprivRegisterTestClass {
|
||||
public:
|
||||
testingprivRegisterTestClass(const char *name, void (*f)(testingT *)) { testingprivRegisterTest(name, f); }
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,144 @@
|
||||
// 27 february 2018
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <setjmp.h>
|
||||
#include "testing.h"
|
||||
|
||||
#define testingprivNew(T) ((T *) malloc(sizeof (T)))
|
||||
|
||||
struct defer {
|
||||
void (*f)(void *);
|
||||
void *data;
|
||||
struct defer *next;
|
||||
};
|
||||
|
||||
struct testingT {
|
||||
const char *name;
|
||||
void (*f)(testingT *);
|
||||
int failed;
|
||||
int skipped;
|
||||
jmp_buf returnNowBuf;
|
||||
struct defer *defers;
|
||||
int defersRun;
|
||||
testingT *next;
|
||||
};
|
||||
|
||||
static testingT *tests = NULL;
|
||||
|
||||
void testingprivRegisterTest(const char *name, void (*f)(testingT *))
|
||||
{
|
||||
testingT *t;
|
||||
|
||||
t = testingprivNew(testingT);
|
||||
t->name = name;
|
||||
t->f = f;
|
||||
t->failed = 0;
|
||||
t->skipped = 0;
|
||||
t->defers = NULL;
|
||||
t->defersRun = 0;
|
||||
// TODO add in the order called
|
||||
t->next = tests;
|
||||
tests = t;
|
||||
}
|
||||
|
||||
static void runDefers(testingT *t)
|
||||
{
|
||||
struct defer *d;
|
||||
|
||||
if (t->defersRun)
|
||||
return;
|
||||
t->defersRun = 1;
|
||||
for (d = t->defers; d != NULL; d = d->next)
|
||||
(*(d->f))(d->data);
|
||||
}
|
||||
|
||||
int testingMain(void)
|
||||
{
|
||||
testingT *t;
|
||||
int anyFailed;
|
||||
const char *status;
|
||||
|
||||
// TODO see if this should run if all tests are skipped
|
||||
if (tests == NULL) {
|
||||
fprintf(stderr, "warning: no tests to run\n");
|
||||
// imitate Go here (TODO confirm this)
|
||||
return 0;
|
||||
}
|
||||
|
||||
anyFailed = 0;
|
||||
for (t = tests; t != NULL; t = t->next) {
|
||||
printf("=== RUN %s\n", t->name);
|
||||
if (setjmp(t->returnNowBuf) == 0)
|
||||
(*(t->f))(t);
|
||||
runDefers(t);
|
||||
status = "PASS";
|
||||
if (t->failed) {
|
||||
status = "FAIL";
|
||||
anyFailed = 1;
|
||||
} else if (t->skipped)
|
||||
// note that failed overrides skipped
|
||||
status = "SKIP";
|
||||
printf("--- %s: %s (%s)\n", status, t->name, "TODO");
|
||||
}
|
||||
|
||||
if (anyFailed) {
|
||||
printf("FAIL\n");
|
||||
return 1;
|
||||
}
|
||||
printf("PASS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void testingprivTLogfFull(testingT *t, const char *file, int line, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
testingprivTLogvfFull(t, file, line, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void testingprivTLogvfFull(testingT *t, const char *file, int line, const char *format, va_list ap)
|
||||
{
|
||||
// TODO extract filename from file
|
||||
printf("\t%s:%d: ", file, line);
|
||||
// TODO split into lines separated by \n\t\t and trimming trailing empty lines
|
||||
vprintf(format, ap);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void testingTFail(testingT *t)
|
||||
{
|
||||
t->failed = 1;
|
||||
}
|
||||
|
||||
static void returnNow(testingT *t)
|
||||
{
|
||||
// run defers before calling longjmp() just to be safe
|
||||
runDefers(t);
|
||||
longjmp(t->returnNowBuf, 1);
|
||||
}
|
||||
|
||||
void testingprivTDoFailNow(testingT *t)
|
||||
{
|
||||
testingTFail(t);
|
||||
returnNow(t);
|
||||
}
|
||||
|
||||
void testingprivTDoSkipNow(testingT *t)
|
||||
{
|
||||
t->skipped = 1;
|
||||
returnNow(t);
|
||||
}
|
||||
|
||||
void testingTDefer(testingT *t, void (*f)(void *data), void *data)
|
||||
{
|
||||
struct defer *d;
|
||||
|
||||
d = testingprivNew(struct defer);
|
||||
d->f = f;
|
||||
d->data = data;
|
||||
// add to the head of the list so defers are run in reverse order of how they were added
|
||||
d->next = t->defers;
|
||||
t->defers = d;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Proper vertical text support in uiDrawTextLayout was removed because DirectWrite doesn't add this until Windows 8.1 (unless I drop IDWriteTextLayout and do the script analysis myself; TODO consider this possibility).
|
||||
|
||||
On OS X, setting the vertical forms attribute stacks non-vertical scripts in vertical text (rotates each individual glyph) with Core Text, whereas everything else — including Cocoa's text system — rotates entire non-vertical strings. Not sure what to do about this except manually detect which characters to apply the attribute to:
|
||||
http://www.unicode.org/notes/tn22/RobustVerticalLayout.pdf
|
||||
http://www.unicode.org/Public/vertical/revision-17/VerticalOrientation-17.txt
|
||||
|
||||
In addition, with Core Text, the vertical forms attribute vertically centers the vertical glyphs on the bhorizontal baseline, rather than flush with the text. Using the baseline class attribute doesn't seem to work.
|
||||
TODO investigate kCJKVerticalRomanPlacementType
|
||||
|
||||
If readded, this will need to be a layout-wide setting, not a per-character setting. Pango works right this way; the current Pango code doesn't seem to work.
|
||||
|
||||
More links:
|
||||
https://www.w3.org/TR/2012/NOTE-jlreq-20120403/#line-composition
|
||||
https://www.w3.org/TR/REC-CSS2/notes.html
|
||||
|
||||
TODO indicate where in the attributes.c file that block of code should go (or drop it entirely for the reasons listed above)
|
||||
TODO same for ui.h
|
||||
|
||||
TODO vertical carets
|
||||
@@ -0,0 +1,19 @@
|
||||
case uiAttributeVerticalForms:
|
||||
if (spec->Value != 0) {
|
||||
CFAttributedStringSetAttribute(p->mas, range, kCTVerticalFormsAttributeName, kCFBooleanTrue);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassRoman);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicCentered);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicLow);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicHigh);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassHanging);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassMath);
|
||||
} else {
|
||||
CFAttributedStringSetAttribute(p->mas, range, kCTVerticalFormsAttributeName, kCFBooleanFalse);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassRoman);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicCentered);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicLow);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicHigh);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassHanging);
|
||||
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassMath);
|
||||
}
|
||||
break;
|
||||
@@ -0,0 +1,9 @@
|
||||
PangoGravity gravity;
|
||||
|
||||
case uiAttributeVerticalForms:
|
||||
gravity = PANGO_GRAVITY_SOUTH;
|
||||
if (spec->Value != 0)
|
||||
gravity = PANGO_GRAVITY_EAST;
|
||||
addattr(p, start, end,
|
||||
pango_attr_gravity_new(gravity));
|
||||
break;
|
||||
@@ -0,0 +1,15 @@
|
||||
uint32_t vertval;
|
||||
|
||||
case uiAttributeVerticalForms:
|
||||
// LONGTERM 8 and/or 8.1 add other methods for vertical text
|
||||
op.p = p;
|
||||
op.start = start;
|
||||
op.end = end;
|
||||
vertval = 0;
|
||||
if (spec->Value != 0)
|
||||
vertval = 1;
|
||||
doOpenType("vert", vertval, &op);
|
||||
doOpenType("vrt2", vertval, &op);
|
||||
doOpenType("vkrn", vertval, &op);
|
||||
doOpenType("vrtr", vertval, &op);
|
||||
break;
|
||||
@@ -0,0 +1,2 @@
|
||||
case uiAttributeVerticalForms:
|
||||
return boolsEqual(attr, spec);
|
||||
@@ -0,0 +1,18 @@
|
||||
next = "vertical glyph forms";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeVerticalForms;
|
||||
spec.Value = 1;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, " (which you can draw rotated for proper vertical text; for instance, ");
|
||||
next = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A";
|
||||
start = uiAttributedStringLen(attrstr);
|
||||
end = start + strlen(next);
|
||||
uiAttributedStringAppendUnattributed(attrstr, next);
|
||||
spec.Type = uiAttributeVerticalForms;
|
||||
spec.Value = 1;
|
||||
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
|
||||
uiAttributedStringAppendUnattributed(attrstr, ")");
|
||||
|
||||
uiAttributedStringAppendUnattributed(attrstr, ", ");
|
||||
@@ -0,0 +1,2 @@
|
||||
// TODO rename to uiAttributeVertical?
|
||||
uiAttributeVerticalForms, // 0 = off, 1 = on
|
||||
Reference in new issue
Block a user