bug 746975 - update graphite2 library to 1.1.2 release from upstream. r=jdaggett a=akeybl

This commit is contained in:
Jonathan Kew 2012-04-21 08:06:26 +01:00
parent e3aa613f41
commit 89f64a5834
31 changed files with 290 additions and 373 deletions

View File

@ -28,9 +28,9 @@
#include "graphite2/Types.h"
#define GR2_VERSION_MAJOR 2
#define GR2_VERSION_MINOR 0
#define GR2_VERSION_BUGFIX 1
#define GR2_VERSION_MAJOR 1
#define GR2_VERSION_MINOR 1
#define GR2_VERSION_BUGFIX 2
#ifdef __cplusplus
extern "C"

View File

@ -94,7 +94,7 @@ enum gr_attrCode {
/// bidi directionality of this glyph (not implemented)
gr_slatDir,
/// Whether insertion is allowed before this glyph
gr_slatInsert,
gr_slatInsert,
/// Final positioned position of this glyph relative to its parent in x-direction in pixels
gr_slatPosX,
/// Final positioned position of this glyph relative to its parent in y-direction in pixels
@ -119,8 +119,10 @@ enum gr_attrCode {
gr_slatJWeight,
/// Amount this slot mush shrink or stretch in design units
gr_slatJWidth,
/// SubSegment split point
gr_slatSegSplit = gr_slatJStretch + 29,
/// User defined attribute, see subattr for user attr number
gr_slatUserDefn = gr_slatJStretch + 30,
gr_slatUserDefn,
/// not implemented
gr_slatMax,

View File

@ -300,8 +300,8 @@ const bidi_action actionWeak[][10] =
inline uint8 GetDeferredType(bidi_action a) { return (a >> 4) & 0xF; }
inline uint8 GetResolvedType(bidi_action a) { return a & 0xF; }
inline DirCode EmbeddingDirection(int l) { return l & 1 ? R : L; }
inline bool IsDeferredState(bidi_state a) { return (1 << a) & (rtmask | ltmask | acmask | rcmask | rsmask | lcmask | lsmask); }
inline bool IsModifiedClass(DirCode a) { return (1 << a) & (ALmask | NSMmask | ESmask | CSmask | ETmask | ENmask); }
inline bool IsDeferredState(bidi_state a) { return 0 != ((1 << a) & (rtmask | ltmask | acmask | rcmask | rsmask | lcmask | lsmask)); }
inline bool IsModifiedClass(DirCode a) { return 0 != ((1 << a) & (ALmask | NSMmask | ESmask | CSmask | ETmask | ENmask)); }
void SetDeferredRunClass(Slot *s, Slot *sRun, int nval)
{

View File

@ -54,6 +54,7 @@ set(GRAPHITE_HEADERS
../include/graphite2/Font.h
../include/graphite2/Segment.h
../include/graphite2/Types.h
../include/graphite2/Log.h
)
file(GLOB PRIVATE_HEADERS inc/*.h)

View File

@ -56,83 +56,64 @@ bool CachedFace::runGraphite(Segment *seg, const Silf *pSilf) const
assert(pSilf);
pSilf->runGraphite(seg, 0, pSilf->substitutionPass());
SegCache * segCache = NULL;
unsigned int silfIndex = 0;
for (; silfIndex < m_numSilf && &(m_silfs[silfIndex]) != pSilf; ++silfIndex);
if (silfIndex == m_numSilf) return false;
SegCache * const segCache = m_cacheStore->getOrCreate(silfIndex, seg->getFeatures(0));
if (!segCache)
return false;
for (unsigned int i = 0; i < m_numSilf; i++)
{
if (&(m_silfs[i]) == pSilf)
{
break;
}
}
assert(silfIndex < m_numSilf);
assert(m_cacheStore);
segCache = m_cacheStore->getOrCreate(silfIndex, seg->getFeatures(0));
// find where the segment can be broken
Slot * subSegStartSlot = seg->first();
Slot * subSegEndSlot = subSegStartSlot;
uint16 cmapGlyphs[eMaxSpliceSize];
int subSegStart = 0;
bool spaceOnly = true;
for (unsigned int i = 0; i < seg->charInfoCount(); i++)
for (unsigned int i = 0; i < seg->charInfoCount(); ++i)
{
if (i - subSegStart < eMaxSpliceSize)
{
cmapGlyphs[i-subSegStart] = subSegEndSlot->gid();
}
if (!m_cacheStore->isSpaceGlyph(subSegEndSlot->gid()))
{
spaceOnly = false;
}
const unsigned int length = i - subSegStart + 1;
if (length < eMaxSpliceSize)
cmapGlyphs[length-1] = subSegEndSlot->gid();
else return false;
const bool spaceOnly = m_cacheStore->isSpaceGlyph(subSegEndSlot->gid());
// at this stage the character to slot mapping is still 1 to 1
int breakWeight = seg->charinfo(i)->breakWeight();
int nextBreakWeight = (i + 1 < seg->charInfoCount())?
seg->charinfo(i+1)->breakWeight() : 0;
if (((breakWeight > 0) &&
(breakWeight <= gr_breakWord)) ||
(i + 1 == seg->charInfoCount()) ||
m_cacheStore->isSpaceGlyph(subSegEndSlot->gid()) ||
((i + 1 < seg->charInfoCount()) &&
(((nextBreakWeight < 0) &&
(nextBreakWeight >= gr_breakBeforeWord)) ||
(subSegEndSlot->next() && m_cacheStore->isSpaceGlyph(subSegEndSlot->next()->gid())))))
const int breakWeight = seg->charinfo(i)->breakWeight(),
nextBreakWeight = (i + 1 < seg->charInfoCount())?
seg->charinfo(i+1)->breakWeight() : 0;
const uint8 f = seg->charinfo(i)->flags();
if (((spaceOnly
|| (breakWeight > 0 && breakWeight <= gr_breakWord)
|| i + 1 == seg->charInfoCount()
|| ((nextBreakWeight < 0 && nextBreakWeight >= gr_breakBeforeWord)
|| (subSegEndSlot->next() && m_cacheStore->isSpaceGlyph(subSegEndSlot->next()->gid()))))
&& f != 1)
|| f == 2)
{
// record the next slot before any splicing
Slot * nextSlot = subSegEndSlot->next();
if (spaceOnly)
{
// spaces should be left untouched by graphite rules in any sane font
}
else
// spaces should be left untouched by graphite rules in any sane font
if (!spaceOnly)
{
// found a break position, check for a cache of the sub sequence
const SegCacheEntry * entry = (segCache)?
segCache->find(cmapGlyphs, i - subSegStart + 1) : NULL;
const SegCacheEntry * entry = segCache->find(cmapGlyphs, length);
// TODO disable cache for words at start/end of line with contextuals
if (!entry)
{
unsigned int length = i - subSegStart + 1;
SegmentScopeState scopeState = seg->setScope(subSegStartSlot, subSegEndSlot, length);
pSilf->runGraphite(seg, pSilf->substitutionPass(), pSilf->numPasses());
//entry =runGraphiteOnSubSeg(segCache, seg, cmapGlyphs,
// subSegStartSlot, subSegEndSlot,
// subSegStart, i - subSegStart + 1);
if ((length < eMaxSpliceSize) && segCache)
if (length < eMaxSpliceSize)
{
seg->associateChars();
entry = segCache->cache(m_cacheStore, cmapGlyphs, length, seg, subSegStart);
}
seg->removeScope(scopeState);
}
else
{
//seg->splice(subSegStart, i - subSegStart + 1, subSegStartSlot, subSegEndSlot, entry);
seg->splice(subSegStart, i - subSegStart + 1, subSegStartSlot, subSegEndSlot,
seg->splice(subSegStart, length, subSegStartSlot, subSegEndSlot,
entry->first(), entry->glyphLength());
}
}
subSegEndSlot = nextSlot;
subSegStartSlot = nextSlot;
subSegStartSlot = subSegEndSlot = nextSlot;
subSegStart = i + 1;
spaceOnly = true;
}
else
{

View File

@ -112,7 +112,7 @@ uint16 CmapCache::operator [] (const uint32 usv) const throw()
CmapCache::operator bool() const throw()
{
return m_blocks;
return m_blocks != 0;
}
@ -136,6 +136,6 @@ uint16 DirectCmap::operator [] (const uint32 usv) const throw()
DirectCmap::operator bool () const throw()
{
return _ctable;
return _ctable != 0;
}

View File

@ -592,7 +592,7 @@ int32 Machine::Code::run(Machine & m, slotref * & map) const
assert(_own);
assert(*this); // Check we are actually runnable
if (m.slotMap().size() <= _max_ref + m.slotMap().context())
if (m.slotMap().size() <= size_t(_max_ref + m.slotMap().context()))
{
m._status = Machine::slot_offset_out_bounds;
// return (m.slotMap().end() - map);

View File

@ -85,7 +85,7 @@ bool Face::readGraphite()
{
size_t lSilf;
const byte * const pSilf = getTable(Tag::Silf, &lSilf),
* p = pSilf;
* p = pSilf;
if (!p) return false;
const uint32 version = be::read<uint32>(p);

View File

@ -42,7 +42,9 @@ using namespace graphite2;
const void* pMaxp = face.getTable(Tag::maxp);
if (pMaxp == NULL) return false;
m_nGlyphs = m_nGlyphsWithGraphics = (unsigned short)TtfUtil::GlyphCount(pMaxp);
if (TtfUtil::LocaLookup(m_nGlyphs-1, m_pLoca, m_lLoca, m_pHead) == size_t(-1))
return false; // This will fail if m_nGlyphs is wildly out of range.
if (!dumb_font)
{
if ((m_pGlat = face.getTable(Tag::Glat, &m_lGlat)) == NULL) return false;

View File

@ -63,33 +63,29 @@ Pass::~Pass()
delete [] m_rules;
}
bool Pass::readPass(void *pass, size_t pass_length, size_t subtable_base, const Face & face)
bool Pass::readPass(const byte * const pass_start, size_t pass_length, size_t subtable_base, const Face & face)
{
const byte * p = reinterpret_cast<const byte *>(pass),
* const pass_start = p,
const byte * p = pass_start,
* const pass_end = p + pass_length;
size_t numRanges;
if (pass_length < 40) return false;
// Read in basic values
m_immutable = (*p++) & 0x1U;
m_iMaxLoop = *p++;
p++; // skip maxContext
p += sizeof(byte); // skip maxBackup
m_immutable = be::read<byte>(p) & 0x1U;
m_iMaxLoop = be::read<byte>(p);
be::skip<byte>(p,2); // skip maxContext & maxBackup
m_numRules = be::read<uint16>(p);
p += sizeof(uint16); // not sure why we would want this
be::skip<uint16>(p); // fsmOffset - not sure why we would want this
const byte * const pcCode = pass_start + be::read<uint32>(p) - subtable_base,
* const rcCode = pass_start + be::read<uint32>(p) - subtable_base,
* const aCode = pass_start + be::read<uint32>(p) - subtable_base;
p += sizeof(uint32);
be::skip<uint32>(p);
m_sRows = be::read<uint16>(p);
m_sTransition = be::read<uint16>(p);
m_sSuccess = be::read<uint16>(p);
m_sColumns = be::read<uint16>(p);
numRanges = be::read<uint16>(p);
p += sizeof(uint16) // skip searchRange
+ sizeof(uint16) // skip entrySelector
+ sizeof(uint16); // skip rangeShift
be::skip<uint16>(p, 3); // skip searchRange, entrySelector & rangeShift.
assert(p - pass_start == 40);
// Perform some sanity checks.
if ( m_sTransition > m_sRows
@ -101,9 +97,9 @@ bool Pass::readPass(void *pass, size_t pass_length, size_t subtable_base, const
m_numGlyphs = be::peek<uint16>(p + numRanges * 6 - 4) + 1;
// Caculate the start of vairous arrays.
const byte * const ranges = p;
p += numRanges*sizeof(uint16)*3;
be::skip<uint16>(p, numRanges*3);
const byte * const o_rule_map = p;
p += (m_sSuccess + 1)*sizeof(uint16);
be::skip<uint16>(p, m_sSuccess + 1);
// More sanity checks
if ( reinterpret_cast<const byte *>(o_rule_map) > pass_end
@ -111,34 +107,38 @@ bool Pass::readPass(void *pass, size_t pass_length, size_t subtable_base, const
return false;
const size_t numEntries = be::peek<uint16>(o_rule_map + m_sSuccess*sizeof(uint16));
const byte * const rule_map = p;
p += numEntries*sizeof(uint16);
be::skip<uint16>(p, numEntries);
if (p > pass_end) return false;
m_minPreCtxt = *p++;
m_maxPreCtxt = *p++;
m_minPreCtxt = be::read<uint8>(p);
m_maxPreCtxt = be::read<uint8>(p);
const byte * const start_states = p;
p += (m_maxPreCtxt - m_minPreCtxt + 1)*sizeof(int16);
be::skip<int16>(p, m_maxPreCtxt - m_minPreCtxt + 1);
const uint16 * const sort_keys = reinterpret_cast<const uint16 *>(p);
p += m_numRules*sizeof(uint16);
be::skip<uint16>(p, m_numRules);
const byte * const precontext = p;
p += m_numRules;
p += sizeof(byte); // skip reserved byte
be::skip<byte>(p, m_numRules);
be::skip<byte>(p); // skip reserved byte
if (p > pass_end) return false;
const size_t pass_constraint_len = be::read<uint16>(p);
const uint16 * const o_constraint = reinterpret_cast<const uint16 *>(p);
p += (m_numRules + 1)*sizeof(uint16);
be::skip<uint16>(p, m_numRules + 1);
const uint16 * const o_actions = reinterpret_cast<const uint16 *>(p);
p += (m_numRules + 1)*sizeof(uint16);
be::skip<uint16>(p, m_numRules + 1);
const byte * const states = p;
p += m_sTransition*m_sColumns*sizeof(int16);
p += sizeof(byte); // skip reserved byte
be::skip<int16>(p, m_sTransition*m_sColumns);
be::skip<byte>(p); // skip reserved byte
if (p != pcCode || p >= pass_end) return false;
p += pass_constraint_len;
if (p != rcCode || p >= pass_end) return false;
p += be::peek<uint16>(o_constraint + m_numRules);
be::skip<byte>(p, pass_constraint_len);
if (p != rcCode || p >= pass_end
|| size_t(rcCode - pcCode) != pass_constraint_len) return false;
be::skip<byte>(p, be::peek<uint16>(o_constraint + m_numRules));
if (p != aCode || p >= pass_end) return false;
if (size_t(rcCode - pcCode) != pass_constraint_len) return false;
be::skip<byte>(p, be::peek<uint16>(o_actions + m_numRules));
// We should be at the end or within the pass
if (p > pass_end) return false;
// Load the pass constraint if there is one.
if (pass_constraint_len)
@ -475,14 +475,15 @@ bool Pass::testPassConstraint(Machine & m) const
}
bool Pass::testConstraint(const Rule &r, Machine & m) const
bool Pass::testConstraint(const Rule & r, Machine & m) const
{
if ((r.sort - r.preContext) > (m.slotMap().size() - m.slotMap().context())) return false;
if (m.slotMap().context() - r.preContext < 0) return false;
if (!*r.constraint) return true;
const uint16 curr_context = m.slotMap().context();
if (unsigned(r.sort - r.preContext) > m.slotMap().size() - curr_context
|| curr_context - r.preContext < 0) return false;
if (!*r.constraint) return true;
assert(r.constraint->constraint());
vm::slotref * map = m.slotMap().begin() + m.slotMap().context() - r.preContext;
vm::slotref * map = m.slotMap().begin() + curr_context - r.preContext;
for (int n = r.sort; n && map; --n, ++map)
{
if (!*map) continue;

View File

@ -144,7 +144,7 @@ SegCacheEntry* SegCache::cache(SegCacheStore * store, const uint16* cmapGlyphs,
void SegCache::purge(SegCacheStore * store)
{
unsigned long long minAccessCount = m_totalAccessCount * m_purgeFactor + 1;
unsigned long long minAccessCount = static_cast<unsigned long long>(m_totalAccessCount * m_purgeFactor + 1);
if (minAccessCount < 2) minAccessCount = 2;
unsigned long long oldAccessTime = m_totalAccessCount - store->maxSegmentCount() / eAgeFactor;
purgeLevel(store, m_prefixes, 0, minAccessCount, oldAccessTime);

View File

@ -52,31 +52,19 @@ SegCacheEntry::SegCacheEntry(const uint16* cmapGlyphs, size_t length, Segment *
m_glyphLength = glyphCount;
Slot * slotCopy = m_glyph;
m_glyph->prev(NULL);
struct Index2Slot {
Index2Slot(uint16 i, const Slot * s) : m_i(i), m_slot(s) {};
Index2Slot() : m_i(0), m_slot(NULL) {};
uint16 m_i;
const Slot * m_slot;
};
struct Index2Slot parentGlyphs[eMaxSpliceSize];
struct Index2Slot childGlyphs[eMaxSpliceSize];
uint16 numParents = 0;
uint16 numChildren = 0;
uint16 pos = 0;
while (slot)
{
slotCopy->userAttrs(m_attr + pos * seg->numAttrs());
slotCopy->set(*slot, -static_cast<int32>(charOffset), seg->numAttrs());
slotCopy->index(pos);
if (slot->firstChild())
{
new(parentGlyphs + numParents) Index2Slot( pos, slot );
++numParents;
}
slotCopy->m_child = m_glyph + slot->firstChild()->index();
if (slot->attachedTo())
{
new(childGlyphs + numChildren) Index2Slot( pos, slot );
++numChildren;
}
slotCopy->attachTo(m_glyph + slot->attachedTo()->index());
if (slot->nextSibling())
slotCopy->m_sibling = m_glyph + slot->nextSibling()->index();
slot = slot->next();
++slotCopy;
++pos;
@ -86,45 +74,6 @@ SegCacheEntry::SegCacheEntry(const uint16* cmapGlyphs, size_t length, Segment *
(slotCopy-1)->next(slotCopy);
}
}
// loop over the attached children finding their siblings and parents
for (int16 i = 0; i < numChildren; i++)
{
if (childGlyphs[i].m_slot->nextSibling())
{
for (int16 j = i; j < numChildren; j++)
{
if (childGlyphs[i].m_slot->nextSibling() == childGlyphs[j].m_slot)
{
m_glyph[childGlyphs[i].m_i].sibling(m_glyph + childGlyphs[j].m_i);
break;
}
}
if (!m_glyph[childGlyphs[i].m_i].nextSibling())
{
// search backwards
for (int16 j = i-1; j >= 0; j--)
{
if (childGlyphs[i].m_slot->nextSibling() == childGlyphs[j].m_slot)
{
m_glyph[childGlyphs[i].m_i].sibling(m_glyph + childGlyphs[j].m_i);
break;
}
}
}
}
// now find the parent glyph
for (int16 j = 0; j < numParents; j++)
{
if (childGlyphs[i].m_slot->attachedTo() == parentGlyphs[j].m_slot)
{
m_glyph[childGlyphs[i].m_i].attachTo(m_glyph + parentGlyphs[j].m_i);
if (parentGlyphs[j].m_slot->firstChild() == childGlyphs[i].m_slot)
{
m_glyph[parentGlyphs[j].m_i].child(m_glyph + childGlyphs[i].m_i);
}
}
}
}
}

View File

@ -204,86 +204,57 @@ void Segment::freeSlot(Slot *aSlot)
}
#ifndef GRAPHITE2_NSEGCACHE
void Segment::splice(size_t offset, size_t length, Slot * startSlot,
Slot * endSlot, const Slot * firstSpliceSlot,
size_t numGlyphs)
void Segment::splice(size_t offset, size_t length, Slot * const startSlot,
Slot * endSlot, const Slot * srcSlot,
const size_t numGlyphs)
{
const Slot * replacement = firstSpliceSlot;
Slot * slot = startSlot;
extendLength(numGlyphs - length);
// insert extra slots if needed
while (numGlyphs > length)
{
Slot * extra = newSlot();
extra->prev(endSlot);
extra->next(endSlot->next());
endSlot->next(extra);
if (extra->next())
extra->next()->prev(extra);
if (m_last == endSlot)
m_last = extra;
endSlot = extra;
++length;
}
// remove any extra
if (numGlyphs < length)
{
Slot * afterSplice = endSlot->next();
Slot * end = endSlot->next();
do
{
endSlot = endSlot->prev();
freeSlot(endSlot->next());
--length;
} while (numGlyphs < length);
endSlot->next(afterSplice);
if (afterSplice)
afterSplice->prev(endSlot);
} while (numGlyphs < --length);
endSlot->next(end);
if (end)
end->prev(endSlot);
}
assert(numGlyphs == length);
// keep a record of consecutive slots wrt start of splice to minimize
// iterative next/prev calls
Slot * slotArray[eMaxSpliceSize];
uint16 slotPosition = 0;
for (uint16 i = 0; i < numGlyphs; i++)
else
{
if (slotPosition <= i)
// insert extra slots if needed
while (numGlyphs > length)
{
slotArray[i] = slot;
slotPosition = i;
Slot * extra = newSlot();
extra->prev(endSlot);
extra->next(endSlot->next());
endSlot->next(extra);
if (extra->next())
extra->next()->prev(extra);
if (m_last == endSlot)
m_last = extra;
endSlot = extra;
++length;
}
slot->set(*replacement, offset, m_silf->numUser());
if (replacement->attachedTo())
{
uint16 parentPos = replacement->attachedTo() - firstSpliceSlot;
while (slotPosition < parentPos)
{
slotArray[slotPosition+1] = slotArray[slotPosition]->next();
++slotPosition;
}
slot->attachTo(slotArray[parentPos]);
}
if (replacement->nextSibling())
{
uint16 pos = replacement->nextSibling() - firstSpliceSlot;
while (slotPosition < pos)
{
slotArray[slotPosition+1] = slotArray[slotPosition]->next();
++slotPosition;
}
slot->sibling(slotArray[pos]);
}
if (replacement->firstChild())
{
uint16 pos = replacement->firstChild() - firstSpliceSlot;
while (slotPosition < pos)
{
slotArray[slotPosition+1] = slotArray[slotPosition]->next();
++slotPosition;
}
slot->child(slotArray[pos]);
}
slot = slot->next();
replacement = replacement->next();
}
endSlot = endSlot->next();
assert(numGlyphs == length);
Slot * indexmap[eMaxSpliceSize*3];
assert(numGlyphs < sizeof indexmap/sizeof *indexmap);
Slot * slot = startSlot;
for (uint16 i=0; i < numGlyphs; slot = slot->next(), ++i)
indexmap[i] = slot;
slot = startSlot;
for (slot=startSlot; slot != endSlot; slot = slot->next(), srcSlot = srcSlot->next())
{
slot->set(*srcSlot, offset, m_silf->numUser());
if (srcSlot->attachedTo()) slot->attachTo(indexmap[srcSlot->attachedTo()->index()]);
if (srcSlot->nextSibling()) slot->m_sibling = indexmap[srcSlot->nextSibling()->index()];
if (srcSlot->firstChild()) slot->m_child = indexmap[srcSlot->firstChild()->index()];
}
}
#endif // GRAPHITE2_NSEGCACHE
@ -347,6 +318,24 @@ Position Segment::positionSlots(const Font *font, Slot * iStart, Slot * iEnd)
}
void Segment::associateChars()
{
int i = 0;
for (Slot * s = m_first; s; s->index(i++), s = s->next())
{
int j = s->before();
if (j < 0) continue;
for (const int after = s->after(); j <= after; ++j)
{
CharInfo & c = *charinfo(j);
if (c.before() == -1 || i < c.before()) c.before(i);
if (c.after() < i) c.after(i);
}
}
}
template <typename utf_iter>
inline void process_utf_data(Segment & seg, const Face & face, const int fid, utf_iter c, size_t n_chars)
{
@ -382,38 +371,16 @@ void Segment::prepare_pos(const Font * /*font*/)
// copy key changeable metrics into slot (if any);
}
void Segment::finalise(const Font *font)
{
if (!m_first) return;
m_advance = positionSlots(font);
int i = 0;
for (Slot * s = m_first; s; s->index(i++), s = s->next())
{
int j = s->before();
if (j < 0) continue;
for (const int after = s->after(); j <= after; ++j)
{
CharInfo & c = *charinfo(j);
if (c.before() == -1 || i < c.before()) c.before(i);
if (c.after() < i) c.after(i);
}
}
linkClusters(m_first, m_last);
}
void Segment::justify(Slot *pSlot, const Font *font, float width, GR_MAYBE_UNUSED justFlags flags, Slot *pFirst, Slot *pLast)
{
Slot *pEnd = pSlot;
Slot *s, *end;
int numBase = 0;
float currWidth = 0.;
float scale = font ? font->scale() : 1.0;
float base;
float currWidth = 0.0;
const float scale = font ? font->scale() : 1.0f;
if (!pFirst) pFirst = pSlot;
base = pFirst->origin().x / scale;
const float base = pFirst->origin().x / scale;
width = width / scale;
end = pLast ? pLast->next() : NULL;
@ -471,7 +438,7 @@ void Segment::bidiPass(uint8 aBidi, int paradir, uint8 aMirror)
unsigned int bmask = 0;
for (s = first(); s; s = s->next())
{
unsigned int bAttr = glyphAttr(s->gid(), aBidi);
unsigned int bAttr = glyphAttr(s->gid(), aBidi);
s->setBidiClass((bAttr <= 16) * bAttr);
bmask |= (1 << s->getBidiClass());
s->setBidiLevel(baseLevel);
@ -486,7 +453,7 @@ void Segment::bidiPass(uint8 aBidi, int paradir, uint8 aMirror)
resolveNeutrals(baseLevel, first());
resolveImplicit(first(), this, aMirror);
resolveWhitespace(baseLevel, this, aBidi, last());
s = resolveOrder(s = first(), baseLevel);
s = resolveOrder(s = first(), baseLevel != 0);
first(s); last(s->prev());
s->prev()->next(0); s->prev(0);
}

View File

@ -64,86 +64,69 @@ void Silf::releaseBuffers() throw()
}
bool Silf::readGraphite(const void* pSilf, size_t lSilf, const Face& face, uint32 version)
bool Silf::readGraphite(const byte * const silf_start, size_t lSilf, const Face& face, uint32 version)
{
const byte * p = (byte *)pSilf,
* const eSilf = p + lSilf;
const byte * p = silf_start,
* const silf_end = p + lSilf;
if (version >= 0x00030000)
{
if (lSilf < 27) { releaseBuffers(); return false; }
p += 8;
if (lSilf < 28) { releaseBuffers(); return false; }
be::skip<int32>(p); // ruleVersion
be::skip<uint16>(p,2); // passOffset & pseudosOffset
}
else if (lSilf < 19) { releaseBuffers(); return false; }
p += 2; // maxGlyphID
p += 4; // extra ascent/descent
m_numPasses = uint8(*p++);
if (m_numPasses > 128)
return false;
m_passes = new Pass[m_numPasses];
m_sPass = uint8(*p++);
m_pPass = uint8(*p++);
if (m_pPass < m_sPass) {
releaseBuffers();
return false;
}
m_jPass = uint8(*p++);
if (m_jPass < m_pPass) {
releaseBuffers();
return false;
}
m_bPass = uint8(*p++); // when do we reorder?
if (m_bPass != 0xFF && (m_bPass < m_jPass || m_bPass > m_numPasses)) {
releaseBuffers();
return false;
}
m_flags = uint8(*p++);
p += 2; // ignore line end contextuals for now
m_aPseudo = uint8(*p++);
m_aBreak = uint8(*p++);
m_aBidi = uint8(*p++);
m_aMirror = uint8(*p++);
p += 1; // skip reserved stuff
m_numJusts = uint8(*p++);
else if (lSilf < 20) { releaseBuffers(); return false; }
be::skip<uint16>(p); // maxGlyphID
be::skip<int16>(p,2); // extra ascent & descent
m_numPasses = be::read<uint8>(p);
m_sPass = be::read<uint8>(p);
m_pPass = be::read<uint8>(p);
m_jPass = be::read<uint8>(p);
m_bPass = be::read<uint8>(p);
m_flags = be::read<uint8>(p);
be::skip<uint8>(p,2); // max{Pre,Post}Context.
m_aPseudo = be::read<uint8>(p);
m_aBreak = be::read<uint8>(p);
m_aBidi = be::read<uint8>(p);
m_aMirror = be::read<uint8>(p);
be::skip<byte>(p); // skip reserved stuff
// Read Justification levels.
m_numJusts = be::read<uint8>(p);
if (p + m_numJusts * 8 >= silf_end) { releaseBuffers(); return false; }
m_justs = gralloc<Justinfo>(m_numJusts);
for (uint8 i = 0; i < m_numJusts; i++)
{
::new(m_justs + i) Justinfo(p[0], p[1], p[2], p[3]);
p += 8;
be::skip<byte>(p,8);
}
// p += uint8(*p) * 8 + 1; // ignore justification for now
if (p + 9 >= eSilf) { releaseBuffers(); return false; }
m_aLig = be::read<uint16>(p);
if (m_aLig > 127) {
releaseBuffers();
return false;
if (p + sizeof(uint16) + sizeof(uint8)*8 >= silf_end) { releaseBuffers(); return false; }
m_aLig = be::read<uint16>(p);
m_aUser = be::read<uint8>(p);
m_iMaxComp = be::read<uint8>(p);
be::skip<byte>(p,5); // direction and 4 reserved bytes
be::skip<uint16>(p, be::read<uint8>(p)); // don't need critical features yet
be::skip<byte>(p); // reserved
if (p >= silf_end) { releaseBuffers(); return false; }
be::skip<uint32>(p, be::read<uint8>(p)); // don't use scriptTag array.
be::skip<uint16>(p); // lbGID
const byte * o_passes = p,
* const passes_start = silf_start + be::read<uint32>(p);
if (m_numPasses > 128 || passes_start >= silf_end
|| m_pPass < m_sPass
|| m_jPass < m_pPass
|| (m_bPass != 0xFF && (m_bPass < m_jPass || m_bPass > m_numPasses))
|| m_aLig > 127) {
releaseBuffers(); return false;
}
m_aUser = uint8(*p++);
m_iMaxComp = uint8(*p++);
p += 5; // skip direction and reserved
p += uint8(*p) * 2 + 1; // don't need critical features yet
p++; // reserved
if (p >= eSilf)
{
releaseBuffers();
return false;
}
p += uint8(*p) * 4 + 1; // skip scripts
p += 2; // skip lbGID
if (p + 4 * (m_numPasses + 1) + 6 >= eSilf)
{
releaseBuffers();
return false;
}
const byte * pPasses = p;
p += 4 * (m_numPasses + 1);
be::skip<uint32>(p, m_numPasses);
if (p + sizeof(uint16) >= passes_start) { releaseBuffers(); return false; }
m_numPseudo = be::read<uint16>(p);
p += 6;
if (p + m_numPseudo * 6 >= eSilf)
{
releaseBuffers();
return false;
be::skip<uint16>(p, 3); // searchPseudo, pseudoSelector, pseudoShift
if (p + m_numPseudo*(sizeof(uint32) + sizeof(uint16)) >= passes_start) {
releaseBuffers(); return false;
}
m_pseudos = new Pseudo[m_numPseudo];
for (int i = 0; i < m_numPseudo; i++)
@ -151,30 +134,21 @@ bool Silf::readGraphite(const void* pSilf, size_t lSilf, const Face& face, uint3
m_pseudos[i].uid = be::read<uint32>(p);
m_pseudos[i].gid = be::read<uint16>(p);
}
if (p >= eSilf)
{
releaseBuffers();
return false;
}
int clen = readClassMap(p, be::peek<uint32>(pPasses) - (p - (byte *)pSilf), version);
if (clen < 0) {
releaseBuffers();
return false;
}
p += clen;
const size_t clen = readClassMap(p, passes_start - p, version);
if (clen == 0 || p + clen > passes_start) { releaseBuffers(); return false; }
m_passes = new Pass[m_numPasses];
for (size_t i = 0; i < m_numPasses; ++i)
{
uint32 pOffset = be::read<uint32>(pPasses);
uint32 pEnd = be::peek<uint32>(pPasses);
if ((uint8 *)pSilf + pEnd > eSilf || pOffset > pEnd)
{
releaseBuffers();
return false;
const byte * const pass_start = silf_start + be::read<uint32>(o_passes),
* const pass_end = silf_start + be::peek<uint32>(o_passes);
if (pass_start > pass_end || pass_end > silf_end) {
releaseBuffers(); return false;
}
m_passes[i].init(this);
if (!m_passes[i].readPass((char *)pSilf + pOffset, pEnd - pOffset, pOffset, face))
if (!m_passes[i].readPass(pass_start, pass_end - pass_start, pass_start - silf_start, face))
{
releaseBuffers();
return false;
@ -204,7 +178,7 @@ template<typename T> inline uint32 Silf::readClassOffsets(const byte *&p, size_t
size_t Silf::readClassMap(const byte *p, size_t data_len, uint32 version)
{
if (data_len < sizeof(uint16)*2) return -1;
if (data_len < sizeof(uint16)*2) return 0;
m_nClass = be::read<uint16>(p);
m_nLinear = be::read<uint16>(p);
@ -213,7 +187,7 @@ size_t Silf::readClassMap(const byte *p, size_t data_len, uint32 version)
// that there is at least enough data for numClasses offsets.
if (m_nLinear > m_nClass
|| (m_nClass + 1) * (version >= 0x00040000 ? sizeof(uint32) : sizeof(uint16))> (data_len - 4))
return -1;
return 0;
uint32 max_off;
@ -222,12 +196,12 @@ size_t Silf::readClassMap(const byte *p, size_t data_len, uint32 version)
else
max_off = readClassOffsets<uint16>(p, data_len);
if (max_off == 0) return -1;
if (max_off == 0) return 0;
// Check the linear offsets are sane, these must be monotonically increasing.
for (const uint32 *o = m_classOffsets, * const o_end = o + m_nLinear; o != o_end; ++o)
if (o[0] > o[1])
return -1;
return 0;
// Fortunately the class data is all uint16s so we can decode these now
m_classData = gralloc<uint16>(max_off);
@ -241,7 +215,7 @@ size_t Silf::readClassMap(const byte *p, size_t data_len, uint32 version)
if (lookup[0] == 0 // A LookupClass with no looks is a suspicious thing ...
|| lookup[0] > (max_off - *o - 4)/2 // numIDs lookup pairs fits within (start of LookupClass' lookups array, max_off]
|| lookup[3] != lookup[0] - lookup[1]) // rangeShift: numIDs - searchRange
return -1;
return 0;
}
return max_off;
@ -271,7 +245,7 @@ uint16 Silf::findClassIndex(uint16 cid, uint16 gid) const
* max = min + cls[0]*2; // lookups aray is numIDs (cls[0]) uint16 pairs long
do
{
const uint16 * p = min + (-2U & ((max-min)/2));
const uint16 * p = min + (-2 & ((max-min)/2));
if (p[0] > gid) max = p;
else min = p;
}

View File

@ -205,7 +205,7 @@ int Slot::getAttr(const Segment *seg, attrCode ind, uint8 subindex) const
{
case gr_slatAdvX : return int(m_advance.x);
case gr_slatAdvY : return int(m_advance.y);
case gr_slatAttTo : return 0;
case gr_slatAttTo : return m_parent ? 1 : 0;
case gr_slatAttX : return int(m_attach.x);
case gr_slatAttY : return int(m_attach.y);
case gr_slatAttXOff :
@ -229,8 +229,9 @@ int Slot::getAttr(const Segment *seg, attrCode ind, uint8 subindex) const
case gr_slatJShrink :
case gr_slatJStep :
case gr_slatJWeight : return 0;
case gr_slatJWidth : return m_just;
case gr_slatJWidth : return int(m_just);
case gr_slatUserDefn : return m_userAttr[subindex];
case gr_slatSegSplit : return seg->charinfo(m_original)->flags() & 3;
default : return 0;
}
}
@ -291,6 +292,7 @@ void Slot::setAttr(Segment *seg, attrCode ind, uint8 subindex, int16 value, cons
case gr_slatJStep : break;
case gr_slatJWeight : break;
case gr_slatJWidth : m_just = value; break;
case gr_slatSegSplit : seg->charinfo(m_original)->addflags(value & 3); break;
case gr_slatUserDefn : m_userAttr[subindex] = value; break;
default :
break;

View File

@ -50,7 +50,7 @@ sparse::~sparse() throw()
sparse::value sparse::operator [] (int k) const throw()
{
bool g = k < m_nchunks*SIZEOF_CHUNK; // This will be 0 is were out of bounds
value g = value(k < m_nchunks*SIZEOF_CHUNK); // This will be 0 is were out of bounds
k *= g; // Force k to 0 if out of bounds making the map look up safe
const chunk & c = m_array.map[k/SIZEOF_CHUNK];
const mask_t m = c.mask >> (SIZEOF_CHUNK - 1 - (k%SIZEOF_CHUNK));

View File

@ -798,7 +798,7 @@ bool HorMetrics(gid16 nGlyphId, const void * pHmtx, size_t lHmtxSize, const void
size_t cLongHorMetrics = be::swap(phhea->num_long_hor_metrics);
if (nGlyphId < cLongHorMetrics)
{ // glyph id is acceptable
if (nGlyphId * sizeof(Sfnt::HorizontalMetric) > lHmtxSize) return false;
if (nGlyphId * sizeof(Sfnt::HorizontalMetric) >= lHmtxSize) return false;
nAdvWid = be::swap(phmtx[nGlyphId].advance_width);
nLsb = be::swap(phmtx[nGlyphId].left_side_bearing);
}
@ -1149,7 +1149,7 @@ size_t LocaLookup(gid16 nGlyphId,
// CheckTable verifies the index_to_loc_format is valid
if (be::swap(pTable->index_to_loc_format) == Sfnt::FontHeader::ShortIndexLocFormat)
{ // loca entries are two bytes and have been divided by two
if (nGlyphId <= (lLocaSize >> 1) - 1) // allow sentinel value to be accessed
if (nGlyphId < (lLocaSize >> 1) - 1) // allow sentinel value to be accessed
{
const uint16 * pShortTable = reinterpret_cast<const uint16 *>(pLoca);
return (be::peek<uint16>(pShortTable + nGlyphId) << 1);
@ -1158,7 +1158,7 @@ size_t LocaLookup(gid16 nGlyphId,
if (be::swap(pTable->index_to_loc_format) == Sfnt::FontHeader::LongIndexLocFormat)
{ // loca entries are four bytes
if (nGlyphId <= (lLocaSize >> 2) - 1)
if (nGlyphId < (lLocaSize >> 2) - 1)
{
const uint32 * pLongTable = reinterpret_cast<const uint32 *>(pLoca);
return be::peek<uint32>(pLongTable + nGlyphId);

View File

@ -104,7 +104,7 @@ Machine::stack_t Machine::run(const instr * program,
{
assert(program != 0);
assert(_status == finished);
// assert(_status == finished);
// Declare virtual machine registers

View File

@ -107,7 +107,7 @@ Machine::stack_t Machine::run(const instr * program,
slotref * & is)
{
assert(program != 0);
assert(_status == finished);
// assert(_status == finished);
const stack_t *sp = static_cast<const stack_t *>(
direct_run(false, program, data, _stack, is, &_map));

View File

@ -121,14 +121,15 @@ void gr_tag_to_str(gr_uint32 tag, char *str)
}
}
#define zeropad(x) if (x == 0x20202020) x = 0; \
else if ((x & 0x00FFFFFF) == 0x00202020) x = x & 0xFF000000; \
else if ((x & 0x0000FFFF) == 0x00002020) x = x & 0xFFFF0000; \
else if ((x & 0x000000FF) == 0x00000020) x = x & 0xFFFFFF00;
gr_feature_val* gr_face_featureval_for_lang(const gr_face* pFace, gr_uint32 langname/*0 means clone default*/) //clones the features. if none for language, clones the default
{
assert(pFace);
if (langname == 0x20202020) langname = 0;
else if ((langname & 0x00FFFFFF) == 0x00202020) langname = langname & 0xFF000000;
else if ((langname & 0x0000FFFF) == 0x00002020) langname = langname & 0xFFFF0000;
else if ((langname & 0x000000FF) == 0x00000020) langname = langname & 0xFFFFFF00;
zeropad(langname)
return static_cast<gr_feature_val *>(pFace->theSill().cloneFeatures(langname));
}
@ -136,6 +137,7 @@ gr_feature_val* gr_face_featureval_for_lang(const gr_face* pFace, gr_uint32 lang
const gr_feature_ref* gr_face_find_fref(const gr_face* pFace, gr_uint32 featId) //When finished with the FeatureRef, call destroy_FeatureRef
{
assert(pFace);
zeropad(featId)
const FeatureRef* pRef = pFace->featureById(featId);
return static_cast<const gr_feature_ref*>(pRef);
}

View File

@ -41,7 +41,7 @@ bool graphite_start_logging(FILE * logFile, GrLogMask mask)
#if !defined GRAPHITE2_NTRACING
dbgout = new json(logFile);
return dbgout;
return dbgout != 0;
#else
return false;
#endif
@ -68,6 +68,7 @@ json & graphite2::operator << (json & j, const CharInfo & ci) throw()
<< "offset" << ci.base()
<< "unicode" << ci.unicodeChar()
<< "break" << ci.breakWeight()
<< "flags" << ci.flags()
<< "slot" << json::flat << json::object
<< "before" << ci.before()
<< "after" << ci.after()
@ -92,7 +93,8 @@ json & graphite2::operator << (json & j, const dslot & ds) throw()
<< "after" << s.after()
<< json::close
<< "origin" << s.origin()
<< "shift" << Position(s.getAttr(0, gr_slatShiftX, 0), s.getAttr(0, gr_slatShiftY, 0))
<< "shift" << Position(float(s.getAttr(0, gr_slatShiftX, 0)),
float(s.getAttr(0, gr_slatShiftY, 0)))
<< "advance" << s.advancePos()
<< "insert" << s.isInsertBefore()
<< "break" << s.getAttr(&seg, gr_slatBreak, 0);

View File

@ -168,7 +168,7 @@ void gr_seg_justify(gr_segment* pSeg/*not NULL*/, gr_slot* pSlot/*not NULL*/, co
{
assert(pSeg);
assert(pSlot);
pSeg->justify(pSlot, pFont, width, justFlags(flags), pFirst, pLast);
pSeg->justify(pSlot, pFont, float(width), justFlags(flags), pFirst, pLast);
}
} // extern "C"

View File

@ -33,6 +33,7 @@ of the License or (at your option) any later version.
namespace graphite2 {
class SegCacheStore;
class SegCache;
class CachedFace : public Face
{

View File

@ -34,7 +34,7 @@ class CharInfo
{
public:
CharInfo() : m_before(-1), m_after(0) {}
CharInfo() : m_before(-1), m_after(0), m_flags(0) {}
void init(int cid) { m_char = cid; }
unsigned int unicodeChar() const { return m_char; }
void feats(int offset) { m_featureid = offset; }
@ -47,6 +47,8 @@ public:
void before(int val) { m_before = val; }
size_t base() const { return m_base; }
void base(size_t offset) { m_base = offset; }
void addflags(uint8 val) { m_flags |= val; }
uint8 flags() const { return m_flags; }
CLASS_NEW_DELETE
private:
@ -56,6 +58,7 @@ private:
size_t m_base; // offset into input string corresponding to this charinfo
uint8 m_featureid; // index into features list in the segment
int8 m_break; // breakweight coming from lb table
uint8 m_flags; // 0,1 segment split.
};
} // namespace graphite2

View File

@ -154,7 +154,12 @@ private:
inline Machine::Machine(SlotMap & map) throw()
: _map(map), _status(finished)
{
memset(_stack, 0, STACK_GUARD);
// Initialise stack guard +1 entries as the stack pointer points to the
// current top of stack, hence the first push will never write entry 0.
// Initialising the guard space like this is unnecessary and is only
// done to keep valgrind happy during fuzz testing. Hopefully loop
// unrolling will flatten this.
for (size_t n = STACK_GUARD + 1; n; --n) _stack[n-1] = 0;
}
inline SlotMap& Machine::slotMap() const throw()

View File

@ -45,7 +45,7 @@ public:
Pass();
~Pass();
bool readPass(void* pPass, size_t pass_length, size_t subtable_base, const Face & face);
bool readPass(const byte * pPass, size_t pass_length, size_t subtable_base, const Face & face);
void runGraphite(vm::Machine & m, FiniteStateMachine & fsm) const;
void init(Silf *silf) { m_silf = silf; }

View File

@ -32,7 +32,7 @@ class Position
{
public:
Position() : x(0), y(0) { }
Position(float inx, float iny) { x = inx; y = iny; }
Position(const float inx, const float iny) { x = inx; y = iny; }
Position operator + (const Position& a) const { return Position(x + a.x, y + a.y); }
Position operator - (const Position& a) const { return Position(x - a.x, y - a.y); }
Position operator * (const float m) const { return Position(x * m, y * m); }

View File

@ -53,7 +53,7 @@ class Segment;
enum SpliceParam {
/** sub-Segments longer than this are not cached
* (in Unicode code points) */
eMaxSpliceSize = 16
eMaxSpliceSize = 96
};
enum justFlags {
@ -92,8 +92,9 @@ public:
SegmentScopeState setScope(Slot * firstSlot, Slot * lastSlot, size_t subLength);
void removeScope(SegmentScopeState & state);
void append(const Segment &other);
void splice(size_t offset, size_t length, Slot * startSlot, Slot * endSlot,
const Slot * firstSpliceSlot, size_t numGlyphs);
void splice(size_t offset, size_t length, Slot * const startSlot,
Slot * endSlot, const Slot * srcSlot,
const size_t numGlyphs);
#endif
Slot *first() { return m_first; }
void first(Slot *p) { m_first = p; }
@ -103,6 +104,7 @@ public:
Slot *newSlot();
void freeSlot(Slot *);
Position positionSlots(const Font *font, Slot *first=0, Slot *last=0);
void associateChars();
void linkClusters(Slot *first, Slot *last);
uint16 getClassGlyph(uint16 cid, uint16 offset) const { return m_silf->getClassGlyph(cid, offset); }
uint16 findClassIndex(uint16 cid, uint16 gid) const { return m_silf->findClassIndex(cid, gid); }
@ -110,15 +112,7 @@ public:
uint16 getFeature(int index, uint8 findex) const { const FeatureRef* pFR=m_face->theSill().theFeatureMap().featureRef(findex); if (!pFR) return 0; else return pFR->getFeatureVal(m_feats[index]); }
void dir(int8 val) { m_dir = val; }
uint16 glyphAttr(uint16 gid, uint8 gattr) const { return m_face->glyphAttr(gid, gattr); }
uint16 getGlyphMetric(Slot *iSlot, uint8 metric, uint8 attrLevel) const {
if (attrLevel > 0)
{
Slot *is = findRoot(iSlot);
return is->clusterMetric(this, metric, attrLevel);
}
else
return m_face->getGlyphMetric(iSlot->gid(), metric);
}
uint16 getGlyphMetric(Slot *iSlot, uint8 metric, uint8 attrLevel) const;
float glyphAdvance(uint16 gid) const { return m_face->getAdvance(gid, 1.0); }
const Rect &theGlyphBBoxTemporary(uint16 gid) const { return m_face->theBBoxTemporary(gid); } //warning value may become invalid when another glyph is accessed
Slot *findRoot(Slot *is) const { return is->attachedTo() ? findRoot(is->attachedTo()) : is; }
@ -160,6 +154,29 @@ private: //defensive on m_charinfo
Segment& operator=(const Segment&);
};
inline
void Segment::finalise(const Font *font)
{
if (!m_first) return;
m_advance = positionSlots(font);
associateChars();
linkClusters(m_first, m_last);
}
inline
uint16 Segment::getGlyphMetric(Slot *iSlot, uint8 metric, uint8 attrLevel) const {
if (attrLevel > 0)
{
Slot *is = findRoot(iSlot);
return is->clusterMetric(this, metric, attrLevel);
}
else
return m_face->getGlyphMetric(iSlot->gid(), metric);
}
} // namespace graphite2
struct gr_segment : public graphite2::Segment {};

View File

@ -64,7 +64,7 @@ public:
Silf() throw();
~Silf() throw();
bool readGraphite(const void *pSilf, size_t lSilf, const Face &face, uint32 version);
bool readGraphite(const byte * const pSilf, size_t lSilf, const Face &face, uint32 version);
bool runGraphite(Segment *seg, uint8 firstPass=0, uint8 lastPass=0) const;
uint16 findClassIndex(uint16 cid, uint16 gid) const;
uint16 getClassGlyph(uint16 cid, unsigned int index) const;

View File

@ -38,6 +38,7 @@ namespace graphite2 {
typedef gr_attrCode attrCode;
class Segment;
class SegCacheEntry;
class Slot
{
@ -134,6 +135,9 @@ private:
byte m_bidiCls; // bidirectional class
byte m_bidiLevel; // bidirectional level
int16 *m_userAttr; // pointer to user attributes
friend class SegCacheEntry;
friend class Segment;
};
} // namespace graphite2

View File

@ -27,7 +27,9 @@ of the License or (at your option) any later version.
// JSON debug logging
// Author: Tim Eves
#include <stdio.h>
#if !defined GRAPHITE2_NTRACING
#include <cstdio>
#include "inc/json.h"
using namespace graphite2;
@ -120,3 +122,5 @@ json & json::operator << (long unsigned d) throw() { context(seq); fprintf(_stre
json & json::operator << (json::boolean b) throw() { context(seq); fputs(b ? "true" : "false", _stream); return *this; }
json & json::operator << (json::_null_t) throw() { context(seq); fputs("null",_stream); return *this; }
#endif