// -*- related-file-name: "../include/efont/otf.hh" -*- /* otf.{cc,hh} -- OpenType font basics * * Copyright (c) 2002-2004 Eddie Kohler * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. This program is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include // for ntohl() #ifndef static_assert #define static_assert(c) switch (c) case 0: case (c): #endif #define USHORT_AT(d) (ntohs(*(const uint16_t *)(d))) #define ULONG_AT(d) (ntohl(*(const uint32_t *)(d))) #define ULONG_AT2(d) ((USHORT_AT((d)) << 16) | USHORT_AT((d)+2)) namespace Efont { namespace OpenType { Vector debug_glyph_names; Font::Font(const String &s, ErrorHandler *errh) : _str(s) { _str.align(4); _error = parse_header(errh ? errh : ErrorHandler::silent_handler()); } int Font::parse_header(ErrorHandler *errh) { // HEADER FORMAT: // Fixed sfnt version // USHORT numTables // USHORT searchRange // USHORT entrySelector // USHORT rangeShift int len = length(); const uint8_t *data = this->data(); if (HEADER_SIZE > len) return errh->error("OTF too small for header"), -EFAULT; if ((data[0] != 'O' || data[1] != 'T' || data[2] != 'T' || data[3] != 'O') && (data[0] != '\000' || data[1] != '\001')) return errh->error("not an OpenType font (bad magic number)"), -ERANGE; int ntables = USHORT_AT(data + 4); if (ntables == 0) return errh->error("OTF contains no tables"), -EINVAL; if (HEADER_SIZE + TABLE_DIR_ENTRY_SIZE * ntables > len) return errh->error("OTF table directory out of range"), -EFAULT; // TABLE DIRECTORY ENTRY FORMAT: // ULONG tag // ULONG checksum // ULONG offset // ULONG length uint32_t last_tag = 0U; for (int i = 0; i < ntables; i++) { int loc = HEADER_SIZE + TABLE_DIR_ENTRY_SIZE * i; uint32_t tag = ULONG_AT(data + loc); uint32_t offset = ULONG_AT(data + loc + 8); uint32_t length = ULONG_AT(data + loc + 12); if (tag <= last_tag) return errh->error("tags out of order"), -EINVAL; if (offset + length > (uint32_t) len) return errh->error("OTF data for '%s' out of range", Tag(tag).text().c_str()), -EFAULT; last_tag = tag; } return 0; } String Font::table(Tag tag) const { if (error() < 0) return String(); const uint8_t *entry = tag.table_entry(data() + HEADER_SIZE, USHORT_AT(data() + 4), TABLE_DIR_ENTRY_SIZE); if (entry) return _str.substring(ULONG_AT(entry + 8), ULONG_AT(entry + 12)); else return String(); } int Font::ntables() const { if (error() < 0) return 0; else return USHORT_AT(data() + 4); } Tag Font::table_tag(int i) const { if (error() < 0 || i < 0 || i >= ntables()) return Tag(); else return Tag(ULONG_AT(data() + HEADER_SIZE + TABLE_DIR_ENTRY_SIZE * i)); } /************************** * Tag * * * **************************/ Tag::Tag(const char *s) : _tag(0) { if (!s) s = ""; for (int i = 0; i < 4; i++) if (*s == 0) _tag = (_tag << 8) | 0x20; else if (*s < 32 || *s > 126) { // don't care if s is signed _tag = 0; return; } else _tag = (_tag << 8) | *s++; if (*s) _tag = 0; } Tag::Tag(const String &s) : _tag(0) { if (s.length() <= 4) { const char *ss = s.data(); for (int i = 0; i < s.length(); i++, ss++) if (*ss < 32 || *ss > 126) { _tag = 0; return; } else _tag = (_tag << 8) | *ss; for (int i = s.length(); i < 4; i++) _tag = (_tag << 8) | 0x20; } } bool Tag::valid() const { uint32_t tag = _tag; for (int i = 0; i < 4; i++, tag >>= 8) if ((tag & 255) < 32 || (tag & 255) > 126) return false; return true; } String Tag::text() const { StringAccum sa; uint32_t tag = _tag; for (int i = 0; i < 4; i++, tag = (tag << 8) | 0x20) if (tag != 0x20202020) { uint8_t c = (tag >> 24) & 255; if (c < 32 || c > 126) sa.snprintf(6, "\\%03o", c); else sa << c; } return sa.take_string(); } String Tag::langsys_text(Tag script, Tag langsys) { if (!langsys.null()) return script.text() + "." + langsys.text(); else return script.text(); } const uint8_t * Tag::table_entry(const uint8_t *table, int n, int entry_size) const { assert(((uintptr_t)table & 1) == 0); int l = 0; int r = n - 1; while (l <= r) { int m = (l + r) / 2; const uint8_t *entry = table + m * entry_size; uint32_t m_tag = ULONG_AT2(entry); if (_tag < m_tag) r = m - 1; else if (_tag == m_tag) return entry; else l = m + 1; } return 0; } /************************** * ScriptList * * * **************************/ int ScriptList::assign(const String &str, ErrorHandler *errh) { _str = str; _str.align(4); int result = check_header(errh ? errh : ErrorHandler::silent_handler()); if (result < 0) _str = String(); return result; } int ScriptList::check_header(ErrorHandler *errh) { // HEADER FORMAT: // USHORT scriptCount // 6bytes scriptRecord[] int scriptCount; if (_str.length() < SCRIPTLIST_HEADERSIZE || (scriptCount = USHORT_AT(_str.udata()), _str.length() < SCRIPTLIST_HEADERSIZE + scriptCount*SCRIPT_RECSIZE)) return errh->error("OTF ScriptList too short"); // XXX check that scripts are sorted return 0; } int ScriptList::script_offset(Tag script) const { if (_str.length() == 0) return -1; const uint8_t *data = _str.udata(); if (const uint8_t *entry = script.table_entry(data + SCRIPTLIST_HEADERSIZE, USHORT_AT(data), SCRIPT_RECSIZE)) return USHORT_AT(entry + 4); else return 0; } int ScriptList::check_script(Tag tag, int script_off, ErrorHandler *errh) const { const uint8_t *data = _str.udata(); int langSysCount; if (_str.length() < script_off + SCRIPT_HEADERSIZE || (langSysCount = USHORT_AT(data + script_off + 2), (_str.length() < script_off + SCRIPT_HEADERSIZE + langSysCount*LANGSYS_RECSIZE))) return (errh ? errh->error("OTF Script table for '%s' out of range", tag.text().c_str()) : -1); // XXX check that langsys are sorted return 0; } int ScriptList::langsys_offset(Tag script, Tag langsys, ErrorHandler *errh) const { int script_off = script_offset(script); if (script_off == 0) { script = Tag("DFLT"); script_off = script_offset(script); } if (script_off <= 0) return script_off; // check script bounds if (check_script(script, script_off, errh) < 0) return -1; // search script table const uint8_t *data = _str.udata(); int langSysCount = USHORT_AT(data + script_off + 2); if (const uint8_t *entry = langsys.table_entry(data + script_off + SCRIPT_HEADERSIZE, langSysCount, LANGSYS_RECSIZE)) return script_off + USHORT_AT(entry + 4); // return default int defaultLangSys = USHORT_AT(data + script_off); if (defaultLangSys != 0) return script_off + defaultLangSys; else return 0; } int ScriptList::language_systems(Vector &script, Vector &langsys, ErrorHandler *errh) const { script.clear(); langsys.clear(); const uint8_t *data = _str.udata(); int nscripts = USHORT_AT(data); for (int i = 0; i < nscripts; i++) { Tag script_tag(ULONG_AT2(data + SCRIPTLIST_HEADERSIZE + i*SCRIPT_RECSIZE)); int script_off = USHORT_AT(data + SCRIPTLIST_HEADERSIZE + i*SCRIPT_RECSIZE + 4); if (check_script(script_tag, script_off, errh) < 0) return -1; const uint8_t *script_table = data + script_off; if (USHORT_AT(script_table) != 0) // default LangSys script.push_back(script_tag), langsys.push_back(Tag()); int nlangsys = USHORT_AT(script_table + 2); for (int j = 0; j < nlangsys; j++) { Tag langsys_tag(ULONG_AT2(script_table + SCRIPT_HEADERSIZE + j*LANGSYS_RECSIZE)); script.push_back(script_tag), langsys.push_back(langsys_tag); } } return 0; } int ScriptList::features(Tag script, Tag langsys, int &required_fid, Vector &fids, ErrorHandler *errh, bool clear_fids) const { required_fid = -1; if (clear_fids) fids.clear(); int offset = langsys_offset(script, langsys); if (offset <= 0) return offset; // check langsys bounds const uint8_t *data = _str.udata(); int featureCount; if (_str.length() < offset + LANGSYS_HEADERSIZE || (featureCount = USHORT_AT(data + offset + 4), (_str.length() < offset + LANGSYS_HEADERSIZE + featureCount*FEATURE_RECSIZE))) return (errh ? errh->error("OTF LangSys table for '%s/%s' out of range", script.text().c_str(), langsys.text().c_str()) : -1); // search langsys table int f = USHORT_AT(data + offset + 2); if (f != 0xFFFF) required_fid = f; data += offset + 6; for (int i = 0; i < featureCount; i++, data += FEATURE_RECSIZE) fids.push_back(USHORT_AT(data)); return 0; } /************************** * FeatureList * * * **************************/ int FeatureList::assign(const String &str, ErrorHandler *errh) { _str = str; _str.align(2); int result = check_header(errh ? errh : ErrorHandler::silent_handler()); if (result < 0) _str = String(); return result; } int FeatureList::check_header(ErrorHandler *errh) { int featureCount; if (_str.length() < FEATURELIST_HEADERSIZE || (featureCount = USHORT_AT(_str.udata()), _str.length() < FEATURELIST_HEADERSIZE + featureCount*FEATURE_RECSIZE)) return errh->error("OTF FeatureList too short"); return 0; } Tag FeatureList::tag(int fid) const { if (_str.length() == 0) return Tag(); const uint8_t *data = _str.udata(); int nfeatures = USHORT_AT(data); if (fid >= 0 && fid < nfeatures) return Tag(ULONG_AT2(data + FEATURELIST_HEADERSIZE + fid*FEATURE_RECSIZE)); else return Tag(); } String FeatureList::params(int fid, int length, ErrorHandler *errh, bool old_style_offset) const { if (_str.length() == 0 || length < 0) return String(); if (errh == 0) errh = ErrorHandler::silent_handler(); const uint8_t *data = _str.udata(); int len = _str.length(); int nfeatures = USHORT_AT(data); if (fid < 0 || fid >= nfeatures) return errh->error("OTF feature ID '%d' out of range", fid), String(); int foff = USHORT_AT(data + FEATURELIST_HEADERSIZE + fid*FEATURE_RECSIZE + 4); if (len < foff + FEATURE_HEADERSIZE) return errh->error("OTF LookupList for feature ID '%d' too short", fid), String(); int poff = USHORT_AT(data + foff); if (poff == 0) return String(); if (!old_style_offset) poff += foff; if (len < poff + length) return errh->error("OTF feature parameters for feature ID '%d' out of range", fid), String(); else return _str.substring(poff, length); } int FeatureList::find(Tag tag, const Vector &fids) const { if (fids.size() == 0 || _str.length() == 0) return -1; const uint8_t *data = _str.udata(); int nfeatures = USHORT_AT(data); for (const int *fidp = fids.begin(); fidp != fids.end(); fidp++) if (*fidp >= 0 && *fidp < nfeatures) { uint32_t ftag = ULONG_AT2(data + FEATURELIST_HEADERSIZE + (*fidp)*FEATURE_RECSIZE); if (ftag == tag.value()) return *fidp; } return -1; } void FeatureList::filter(Vector &fids, const Vector &sorted_ftags) const { if (_str.length() == 0) fids.clear(); else { std::sort(fids.begin(), fids.end()); // sort fids int i = 0, j = 0; while (i < fids.size() && fids[i] < 0) fids[i++] = 0x7FFFFFFF; // XXX check that feature list is in alphabetical order const uint8_t *data = _str.udata(); int nfeatures = USHORT_AT(data); while (i < fids.size() && j < sorted_ftags.size() && fids[i] < nfeatures) { uint32_t ftag = ULONG_AT2(data + FEATURELIST_HEADERSIZE + fids[i]*FEATURE_RECSIZE); if (ftag < sorted_ftags[j].value()) { // not an interesting feature // replace featureID with a large number, remove later fids[i] = 0x7FFFFFFF; i++; } else if (ftag == sorted_ftags[j].value()) // interesting feature i++; else // an interesting feature is not available j++; } fids.resize(i); // remove remaining uninteresting features std::sort(fids.begin(), fids.end()); // resort, to move bad ones last while (fids.size() && fids.back() == 0x7FFFFFFF) fids.pop_back(); } } int FeatureList::lookups(int fid, Vector &results, ErrorHandler *errh, bool clear_results) const { if (clear_results) results.clear(); if (_str.length() == 0) return -1; if (errh == 0) errh = ErrorHandler::silent_handler(); const uint8_t *data = _str.udata(); int len = _str.length(); int nfeatures = USHORT_AT(data); if (fid < 0 || fid >= nfeatures) return errh->error("OTF feature ID '%d' out of range", fid); int foff = USHORT_AT(data + FEATURELIST_HEADERSIZE + fid*FEATURE_RECSIZE + 4); int lookupCount; if (len < foff + FEATURE_HEADERSIZE || (lookupCount = USHORT_AT(data + foff + 2), len < foff + FEATURE_HEADERSIZE + lookupCount*LOOKUPLIST_RECSIZE)) return errh->error("OTF LookupList for feature ID '%d' too short", fid); const uint8_t *ldata = data + foff + FEATURE_HEADERSIZE; for (int j = 0; j < lookupCount; j++, ldata += LOOKUPLIST_RECSIZE) results.push_back(USHORT_AT(ldata)); return 0; } int FeatureList::lookups(const Vector &fids, Vector &results, ErrorHandler *errh) const { results.clear(); if (_str.length() == 0) return -1; for (int i = 0; i < fids.size(); i++) if (lookups(fids[i], results, errh, false) < 0) return -1; // sort results and remove duplicates std::sort(results.begin(), results.end()); int *unique_result = std::unique(results.begin(), results.end()); results.resize(unique_result - results.begin()); return 0; } int FeatureList::lookups(const Vector &required_fids, const Vector &fids, const Vector &sorted_ftags, Vector &results, ErrorHandler *errh) const { Vector fidsx(fids); filter(fidsx, sorted_ftags); for (int i = 0; i < required_fids.size(); i++) fidsx.push_back(required_fids[i]); return lookups(fidsx, results, errh); } int FeatureList::lookups(int required_fid, const Vector &fids, const Vector &sorted_ftags, Vector &results, ErrorHandler *errh) const { Vector fidsx(fids); filter(fidsx, sorted_ftags); if (required_fid >= 0) fidsx.push_back(required_fid); return lookups(fidsx, results, errh); } int FeatureList::lookups(const ScriptList &script_list, Tag script, Tag langsys, const Vector &sorted_ftags, Vector &results, ErrorHandler *errh) const { int required_fid; Vector fids; int result = script_list.features(script, langsys, required_fid, fids, errh); if (result >= 0) { filter(fids, sorted_ftags); if (required_fid >= 0) fids.push_back(required_fid); result = lookups(fids, results, errh); } return result; } /************************** * Coverage * * * **************************/ Coverage::Coverage() throw () { } Coverage::Coverage(Glyph first, Glyph last) throw () { if (first <= last) { _str = String("\000\002\000\001\000\000\000\000\000\000", 10); uint8_t *data = _str.mutable_udata(); data[4] = first >> 8; data[5] = first & 255; data[6] = last >> 8; data[7] = last & 255; _str.align(2); } } Coverage::Coverage(const String &str, ErrorHandler *errh, bool do_check) throw () : _str(str) { _str.align(2); if (do_check) { if (check(errh ? errh : ErrorHandler::silent_handler()) < 0) _str = String(); } else { // check()'s shorten-string side effect const uint8_t *data = _str.udata(); int count = USHORT_AT(data + 2); if (data[1] == T_LIST) _str = _str.substring(0, HEADERSIZE + count*LIST_RECSIZE); else _str = _str.substring(0, HEADERSIZE + count*RANGES_RECSIZE); } } int Coverage::check(ErrorHandler *errh) // side effect: shorten string to cover coverage { // HEADER FORMAT: // USHORT coverageFormat // USHORT glyphCount const uint8_t *data = _str.udata(); if (_str.length() < HEADERSIZE) return errh->error("OTF coverage table too small for header"); int coverageFormat = USHORT_AT(data); int count = USHORT_AT(data + 2); int len; switch (coverageFormat) { case T_LIST: len = HEADERSIZE + count*LIST_RECSIZE; if (_str.length() < len) return errh->error("OTF coverage table too short (format 1)"); // XXX don't check sorting break; case T_RANGES: len = HEADERSIZE + count*RANGES_RECSIZE; if (_str.length() < len) return errh->error("OTF coverage table too short (format 2)"); // XXX don't check sorting // XXX don't check startCoverageIndexes break; default: return errh->error("OTF coverage table has unknown format %d", coverageFormat); } _str = _str.substring(0, len); return 0; } int Coverage::size() const throw () { if (_str.length() == 0) return -1; const uint8_t *data = _str.udata(); if (data[1] == T_LIST) return (_str.length() - HEADERSIZE) / LIST_RECSIZE; else if (data[1] == T_RANGES) { data += _str.length() - RANGES_RECSIZE; return USHORT_AT(data + 4) + USHORT_AT(data + 2) - USHORT_AT(data) + 1; } else return -1; } int Coverage::coverage_index(Glyph g) const throw () { if (_str.length() == 0) return -1; const uint8_t *data = _str.udata(); int count = USHORT_AT(data + 2); if (data[1] == T_LIST) { int l = 0, r = count - 1; data += HEADERSIZE; while (l <= r) { int m = (l + r) >> 1; int mval = USHORT_AT(data + m * LIST_RECSIZE); if (g < mval) r = m - 1; else if (g == mval) return m; else l = m + 1; } return -1; } else if (data[1] == T_RANGES) { int l = 0, r = count - 1; data += HEADERSIZE; while (l <= r) { int m = (l + r) >> 1; const uint8_t *rec = data + m * RANGES_RECSIZE; if (g < USHORT_AT(rec)) r = m - 1; else if (g <= USHORT_AT(rec + 2)) return USHORT_AT(rec + 4) + g - USHORT_AT(rec); else l = m + 1; } return -1; } else return -1; } Glyph Coverage::operator[](int cindex) const throw () { if (_str.length() == 0 || cindex < 0) return 0; const uint8_t *data = _str.udata(); int count = USHORT_AT(data + 2); if (data[1] == T_LIST) return (cindex < count ? USHORT_AT(data + cindex * LIST_RECSIZE) : 0); else if (data[1] == T_RANGES) { int l = 0, r = count - 1; data += HEADERSIZE; while (l <= r) { int m = (l + r) >> 1; const uint8_t *rec = data + m * RANGES_RECSIZE; int start_cindex = USHORT_AT(rec + 4); if (cindex < start_cindex) r = m - 1; else if (cindex < start_cindex + USHORT_AT(rec + 2) - USHORT_AT(rec)) return USHORT_AT(rec) + cindex - start_cindex; else l = m + 1; } return 0; } else return 0; } void Coverage::unparse(StringAccum &sa) const throw () { const uint8_t *data = _str.udata(); if (_str.length() == 0) sa << "@*#!"; else if (data[1] == T_LIST) { int count = USHORT_AT(data + 2); for (int i = 0; i < count; i++) { if (i) sa << ','; sa << USHORT_AT(data + HEADERSIZE + i*LIST_RECSIZE); } } else { for (int pos = HEADERSIZE; pos < _str.length(); pos += RANGES_RECSIZE) { Glyph start = USHORT_AT(data + pos); Glyph end = USHORT_AT(data + pos + 2); if (pos > HEADERSIZE) sa << ','; sa << start; if (end != start) sa << '.' << '.' << end; } } } String Coverage::unparse() const throw () { StringAccum sa; unparse(sa); return sa.take_string(); } Coverage operator&(const Coverage &a, const Coverage &b) { StringAccum sa; sa << '\000' << '\001' << '\000' << '\000'; Coverage::iterator ai = a.begin(), bi = b.begin(); while (ai && bi) { if (*ai < *bi) ai.forward_to(*bi); else if (*ai == *bi) { uint16_t x = *ai; sa << (char)(x >> 8) << (char)(x & 0xFF); ai++, bi++; } else bi.forward_to(*ai); } int n = (sa.length() - 4) / 2; sa[2] = (n >> 8); sa[3] = (n & 0xFF); return Coverage(sa.take_string(), 0, false); } bool operator<=(const Coverage &a, const Coverage &b) { Coverage::iterator ai = a.begin(), bi = b.begin(); while (ai && bi) { if (*ai != *bi && !bi.forward_to(*ai)) return false; ai++, bi++; } return bi || !ai; } /************************ * Coverage::iterator * * * ************************/ Coverage::iterator::iterator(const String &str, int pos) : _str(str), _pos(pos), _value(0) { // XXX assume _str has been checked // shrink _str to fit the coverage table const uint8_t *data = _str.udata(); if (_str.length()) { int n = USHORT_AT(data + 2); switch (USHORT_AT(data)) { case T_LIST: _str = _str.substring(0, HEADERSIZE + n*LIST_RECSIZE); break; case T_RANGES: _str = _str.substring(0, HEADERSIZE + n*RANGES_RECSIZE); break; default: _str = String(); break; } } if (_pos >= _str.length()) _pos = _str.length(); else { // now, move _pos into the coverage table _pos = HEADERSIZE; _value = (_pos >= _str.length() ? 0 : USHORT_AT(data + _pos)); } } int Coverage::iterator::coverage_index() const { const uint8_t *data = _str.udata(); assert(_pos < _str.length()); if (data[1] == T_LIST) return (_pos - HEADERSIZE) / LIST_RECSIZE; else return USHORT_AT(data + _pos + 4) + _value - USHORT_AT(data + _pos); } void Coverage::iterator::operator++(int) { const uint8_t *data = _str.udata(); int len = _str.length(); if (_pos >= len || (data[1] == T_RANGES && ++_value <= USHORT_AT(data + _pos + 2))) return; _pos += (data[1] == T_LIST ? LIST_RECSIZE : RANGES_RECSIZE); _value = (_pos >= len ? 0 : USHORT_AT(data + _pos)); } bool Coverage::iterator::forward_to(Glyph find) { // XXX really should check that this works if (find <= _value) return find == _value; else if (_pos >= _str.length()) return false; const uint8_t *data = _str.udata(); if (data[1] == T_LIST) { // check for "common" case: next element _pos += LIST_RECSIZE; if (_pos >= _str.length()) return false; else if (find <= USHORT_AT(data + _pos)) { _value = USHORT_AT(data + _pos); return find == _value; } // otherwise, binary search over remaining area int l = ((_pos - HEADERSIZE) / LIST_RECSIZE) + 1; int r = (_str.length() - HEADERSIZE) / LIST_RECSIZE - 1; data += HEADERSIZE; while (l <= r) { int m = (l + r) >> 1; Glyph g = USHORT_AT(data + m * LIST_RECSIZE); if (find < g) r = m - 1; else if (find == g) l = m, r = m - 1; else l = m + 1; } _pos = HEADERSIZE + l * LIST_RECSIZE; _value = (_pos >= _str.length() ? 0 : USHORT_AT(data - HEADERSIZE + _pos)); } else { // data[1] == T_RANGES // check for "common" case: this or next element if (find <= USHORT_AT(data + _pos + 2)) { assert(find >= USHORT_AT(data + _pos)); _value = find; return true; } _pos += RANGES_RECSIZE; if (_pos >= _str.length()) return false; else if (find <= USHORT_AT(data + _pos + 2)) { _value = (find >= USHORT_AT(data + _pos) ? find : USHORT_AT(data + _pos)); return find == _value; } // otherwise, binary search over remaining area int l = ((_pos - HEADERSIZE) / RANGES_RECSIZE) + 1; int r = (_str.length() - HEADERSIZE) / RANGES_RECSIZE - 1; data += HEADERSIZE; while (l <= r) { int m = (l + r) >> 1; if (find < USHORT_AT(data + m * RANGES_RECSIZE)) r = m - 1; else if (find <= USHORT_AT(data + m * RANGES_RECSIZE + 2)) { _pos = HEADERSIZE + m * RANGES_RECSIZE; _value = find; return true; } else l = m + 1; } _pos = HEADERSIZE + l * LIST_RECSIZE; _value = (_pos >= _str.length() ? 0 : USHORT_AT(data - HEADERSIZE + _pos)); } return find == _value; } /************************** * GlyphSet * * * **************************/ GlyphSet::GlyphSet() { memset(_v, 0, sizeof(_v)); } GlyphSet::GlyphSet(const GlyphSet &o) { for (int i = 0; i < VLEN; i++) if (o._v[i]) { _v[i] = new uint32_t[VULEN]; memcpy(_v[i], o._v[i], sizeof(uint32_t) * VULEN); } else _v[i] = 0; } GlyphSet::~GlyphSet() { for (int i = 0; i < VLEN; i++) delete[] _v[i]; } int GlyphSet::change(Glyph g, bool value) { if ((unsigned)g > MAXGLYPH) return -1; uint32_t *&u = _v[g >> SHIFT]; if (!u) { u = new uint32_t[VULEN]; memset(u, 0, sizeof(uint32_t) * VULEN); } uint32_t mask = (1 << (g & 0x1F)); if (value) u[(g & MASK) >> 5] |= mask; else u[(g & MASK) >> 5] &= ~mask; return 0; } GlyphSet & GlyphSet::operator=(const GlyphSet &o) { if (&o != this) { for (int i = 0; i < VLEN; i++) if (o._v[i]) { if (!_v[i]) _v[i] = new uint32_t[VULEN]; memcpy(_v[i], o._v[i], sizeof(uint32_t) * VULEN); } else if (_v[i]) memset(_v[i], 0, sizeof(uint32_t) * VULEN); } return *this; } /************************** * ClassDef * * * **************************/ ClassDef::ClassDef(const String &str, ErrorHandler *errh) throw () : _str(str) { _str.align(2); if (check(errh ? errh : ErrorHandler::silent_handler()) < 0) _str = String(); } int ClassDef::check(ErrorHandler *errh) { // HEADER FORMAT: // USHORT coverageFormat // USHORT glyphCount const uint8_t *data = _str.udata(); if (_str.length() < 6) // NB: prevents empty format-2 tables return errh->error("OTF class def table too small for header"); int classFormat = USHORT_AT(data); int len; if (classFormat == T_LIST) { int count = USHORT_AT(data + 4); len = LIST_HEADERSIZE + count*LIST_RECSIZE; // XXX don't check sorting } else if (classFormat == T_RANGES) { int count = USHORT_AT(data + 2); len = RANGES_HEADERSIZE + count*RANGES_RECSIZE; // XXX don't check sorting } else return errh->error("OTF class def table has unknown format %d", classFormat); if (len > _str.length()) return errh->error("OTF class def table too short"); else { _str = _str.substring(0, len); return 0; } } int ClassDef::lookup(Glyph g) const throw () { if (_str.length() == 0) return -1; const uint8_t *data = _str.udata(); int coverageFormat = USHORT_AT(data); if (coverageFormat == T_LIST) { Glyph start = USHORT_AT(data + 2); int count = USHORT_AT(data + 4); if (g < start || g >= start + count) return 0; else return USHORT_AT(data + LIST_HEADERSIZE + (g - start) * LIST_RECSIZE); } else if (coverageFormat == T_RANGES) { int l = 0, r = USHORT_AT(data + 2) - 1; data += RANGES_HEADERSIZE; while (l <= r) { int m = (l + r) >> 1; const uint8_t *rec = data + m * RANGES_RECSIZE; if (g < USHORT_AT(rec)) r = m - 1; else if (g <= USHORT_AT(rec + 2)) return USHORT_AT(rec + 4); else l = m + 1; } return 0; } else return 0; } void ClassDef::unparse(StringAccum &sa) const throw () { const uint8_t *data = _str.udata(); if (_str.length() == 0) sa << "@*#!"; else if (data[1] == T_LIST) { Glyph start = USHORT_AT(data + 2); int count = USHORT_AT(data + 4); for (int i = 0; i < count; i++) { if (i) sa << ','; sa << start + i << '=' << USHORT_AT(data + LIST_HEADERSIZE + i*LIST_RECSIZE); } } else { for (int pos = RANGES_HEADERSIZE; pos < _str.length(); pos += RANGES_RECSIZE) { Glyph start = USHORT_AT(data + pos); Glyph end = USHORT_AT(data + pos + 2); if (pos > RANGES_HEADERSIZE) sa << ','; sa << start; if (end != start) sa << '.' << '.' << end; sa << '=' << USHORT_AT(data + pos + 4); } } } String ClassDef::unparse() const throw () { StringAccum sa; unparse(sa); return sa.take_string(); } /****************************** * ClassDef::class_iterator * * * ******************************/ ClassDef::class_iterator::class_iterator(const String &str, int pos, int the_class, const Coverage::iterator &coviter) : _str(str), _pos(pos), _class(the_class), _coviter(coviter) { // XXX assume _str has been checked // cannot handle the_class == 0 when coviter doesn't exist if (_class == 0 && !_coviter) throw Error("cannot iterate over ClassDef class 0"); // shrink _str to fit the coverage table, and create a fake coverage // iterator if necessary const uint8_t *data = _str.udata(); if (_str.length()) { switch (USHORT_AT(data)) { case T_LIST: { Glyph start = USHORT_AT(data + 2); int nglyphs = USHORT_AT(data + 4); _str = _str.substring(0, LIST_HEADERSIZE + nglyphs*LIST_RECSIZE); if (!_coviter) _coviter = Coverage(start, start + nglyphs - 1).begin(); if (_class) _coviter.forward_to(start); break; } case T_RANGES: { Glyph start = USHORT_AT(data + RANGES_HEADERSIZE); int nranges = USHORT_AT(data + 2); _str = _str.substring(0, RANGES_HEADERSIZE + nranges*RANGES_RECSIZE); if (!_coviter) { Glyph end = USHORT_AT(data + RANGES_HEADERSIZE + 2 + (nranges - 1)*RANGES_RECSIZE); _coviter = Coverage(start, end).begin(); } if (_class) _coviter.forward_to(start); break; } default: _str = String(); break; } } // move to the first relevant glyph if (_pos >= _str.length()) _pos = _str.length(); else { _pos = 0; (*this)++; } } void ClassDef::class_iterator::increment_class0() { const uint8_t *data = _str.udata(); int len = _str.length(); bool is_list = (data[1] == T_LIST); if (_pos != 0) _coviter++; else _pos = FIRST_POS; if (_pos == FIRST_POS && _coviter) { if (*_coviter < USHORT_AT(data + (is_list ? 2 : RANGES_HEADERSIZE))) return; _pos = (is_list ? LIST_HEADERSIZE : RANGES_HEADERSIZE); } while (_pos > 0 && _pos < len && _coviter) { Glyph g = *_coviter; if (is_list) { _pos = LIST_HEADERSIZE + LIST_RECSIZE*(g - USHORT_AT(data + 2)); if (_pos >= len) break; else if (USHORT_AT(data + _pos) == 0) // _class == 0 return; _coviter++; } else { if (g < USHORT_AT(data + _pos)) // in a zero range return; else if (g > USHORT_AT(data + _pos + 2)) _pos += RANGES_RECSIZE; else if (USHORT_AT(data + _pos + 4) == 0) // _class == 0 return; else _coviter.forward_to(USHORT_AT(data + _pos + 2) + 1); } } if (_coviter) _pos = LAST_POS; else _pos = len; } void ClassDef::class_iterator::operator++(int) { if (_class == 0) { increment_class0(); return; } const uint8_t *data = _str.udata(); int len = _str.length(); bool is_list = (data[1] == T_LIST); if (_pos != 0) _coviter++; else _pos = (is_list ? LIST_HEADERSIZE : RANGES_HEADERSIZE); while (_pos < len && _coviter) { Glyph g = *_coviter; if (is_list) { _pos = LIST_HEADERSIZE + LIST_RECSIZE*(g - USHORT_AT(data + 2)); if (_pos >= len || USHORT_AT(data + _pos) == _class) return; _coviter++; } else { while (_pos < len && (g > USHORT_AT(data + _pos + 2) || USHORT_AT(data + _pos + 4) != _class)) _pos += RANGES_RECSIZE; // now, _pos >= len, or g <= rec.end && class == rec.class if (_pos >= len || g >= USHORT_AT(data + _pos)) return; _coviter.forward_to(USHORT_AT(data + _pos)); } } _pos = len; } }} // template instantiations #include