| 1 | // SPDX-License-Identifier: MIT OR MPL-2.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later |
| 2 | // Copyright 2010, SIL International, All rights reserved. |
| 3 | |
| 4 | #include <cstdio> |
| 5 | |
| 6 | #include "graphite2/Log.h" |
| 7 | #include "inc/debug.h" |
| 8 | #include "inc/CharInfo.h" |
| 9 | #include "inc/Slot.h" |
| 10 | #include "inc/Segment.h" |
| 11 | #include "inc/json.h" |
| 12 | #include "inc/Collider.h" |
| 13 | |
| 14 | #if defined _WIN32 |
| 15 | #include "windows.h" |
| 16 | #endif |
| 17 | |
| 18 | using namespace graphite2; |
| 19 | |
| 20 | #if !defined GRAPHITE2_NTRACING |
| 21 | json *global_log = 0; |
| 22 | #endif |
| 23 | |
| 24 | extern "C" { |
| 25 | |
| 26 | bool gr_start_logging(GR_MAYBE_UNUSED gr_face * face, const char *log_path) |
| 27 | { |
| 28 | if (!log_path) return false; |
| 29 | |
| 30 | #if !defined GRAPHITE2_NTRACING |
| 31 | gr_stop_logging(face); |
| 32 | #if defined _WIN32 |
| 33 | int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, log_path, -1, 0, 0); |
| 34 | if (n == 0 || n > MAX_PATH - 12) return false; |
| 35 | |
| 36 | LPWSTR wlog_path = gralloc<WCHAR>(n); |
| 37 | if (!wlog_path) return false; |
| 38 | FILE *log = 0; |
| 39 | if (wlog_path && MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, log_path, -1, wlog_path, n)) |
| 40 | log = _wfopen(wlog_path, L"wt" ); |
| 41 | |
| 42 | free(wlog_path); |
| 43 | #else // _WIN32 |
| 44 | FILE *log = fopen(log_path, "wt" ); |
| 45 | #endif // _WIN32 |
| 46 | if (!log) return false; |
| 47 | |
| 48 | if (face) |
| 49 | { |
| 50 | face->setLogger(log); |
| 51 | if (!face->logger()) return false; |
| 52 | |
| 53 | *face->logger() << json::array; |
| 54 | #ifdef GRAPHITE2_TELEMETRY |
| 55 | *face->logger() << face->tele; |
| 56 | #endif |
| 57 | } |
| 58 | else |
| 59 | { |
| 60 | global_log = new json(log); |
| 61 | *global_log << json::array; |
| 62 | } |
| 63 | |
| 64 | return true; |
| 65 | #else // GRAPHITE2_NTRACING |
| 66 | return false; |
| 67 | #endif // GRAPHITE2_NTRACING |
| 68 | } |
| 69 | |
| 70 | bool graphite_start_logging(FILE * /* log */, GrLogMask /* mask */) |
| 71 | { |
| 72 | //#if !defined GRAPHITE2_NTRACING |
| 73 | // graphite_stop_logging(); |
| 74 | // |
| 75 | // if (!log) return false; |
| 76 | // |
| 77 | // dbgout = new json(log); |
| 78 | // if (!dbgout) return false; |
| 79 | // |
| 80 | // *dbgout << json::array; |
| 81 | // return true; |
| 82 | //#else |
| 83 | return false; |
| 84 | //#endif |
| 85 | } |
| 86 | |
| 87 | void gr_stop_logging(GR_MAYBE_UNUSED gr_face * face) |
| 88 | { |
| 89 | #if !defined GRAPHITE2_NTRACING |
| 90 | if (face && face->logger()) |
| 91 | { |
| 92 | FILE * log = face->logger()->stream(); |
| 93 | face->setLogger(0); |
| 94 | fclose(log); |
| 95 | } |
| 96 | else if (!face && global_log) |
| 97 | { |
| 98 | FILE * log = global_log->stream(); |
| 99 | delete global_log; |
| 100 | global_log = 0; |
| 101 | fclose(log); |
| 102 | } |
| 103 | #endif |
| 104 | } |
| 105 | |
| 106 | void graphite_stop_logging() |
| 107 | { |
| 108 | // if (dbgout) delete dbgout; |
| 109 | // dbgout = 0; |
| 110 | } |
| 111 | |
| 112 | } // extern "C" |
| 113 | |
| 114 | #ifdef GRAPHITE2_TELEMETRY |
| 115 | size_t * graphite2::telemetry::_category = 0UL; |
| 116 | #endif |
| 117 | |
| 118 | #if !defined GRAPHITE2_NTRACING |
| 119 | |
| 120 | #ifdef GRAPHITE2_TELEMETRY |
| 121 | |
| 122 | json & graphite2::operator << (json & j, const telemetry & t) throw() |
| 123 | { |
| 124 | j << json::object |
| 125 | << "type" << "telemetry" |
| 126 | << "silf" << t.silf |
| 127 | << "states" << t.states |
| 128 | << "starts" << t.starts |
| 129 | << "transitions" << t.transitions |
| 130 | << "glyphs" << t.glyph |
| 131 | << "code" << t.code |
| 132 | << "misc" << t.misc |
| 133 | << "total" << (t.silf + t.states + t.starts + t.transitions + t.glyph + t.code + t.misc) |
| 134 | << json::close; |
| 135 | return j; |
| 136 | } |
| 137 | #else |
| 138 | json & graphite2::operator << (json & j, const telemetry &) throw() |
| 139 | { |
| 140 | return j; |
| 141 | } |
| 142 | #endif |
| 143 | |
| 144 | |
| 145 | json & graphite2::operator << (json & j, const CharInfo & ci) throw() |
| 146 | { |
| 147 | return j << json::object |
| 148 | << "offset" << ci.base() |
| 149 | << "unicode" << ci.unicodeChar() |
| 150 | << "break" << ci.breakWeight() |
| 151 | << "flags" << ci.flags() |
| 152 | << "slot" << json::flat << json::object |
| 153 | << "before" << ci.before() |
| 154 | << "after" << ci.after() |
| 155 | << json::close |
| 156 | << json::close; |
| 157 | } |
| 158 | |
| 159 | |
| 160 | json & graphite2::operator << (json & j, const dslot & ds) throw() |
| 161 | { |
| 162 | assert(ds.first); |
| 163 | assert(ds.second); |
| 164 | const Segment & seg = *ds.first; |
| 165 | const Slot & s = *ds.second; |
| 166 | const SlotCollision *cslot = seg.collisionInfo(ds.second); |
| 167 | |
| 168 | j << json::object |
| 169 | << "id" << objectid(ds) |
| 170 | << "gid" << s.gid() |
| 171 | << "charinfo" << json::flat << json::object |
| 172 | << "original" << s.original() |
| 173 | << "before" << s.before() |
| 174 | << "after" << s.after() |
| 175 | << json::close |
| 176 | << "origin" << s.origin() |
| 177 | << "shift" << Position(float(s.getAttr(0, gr_slatShiftX, 0)), |
| 178 | float(s.getAttr(0, gr_slatShiftY, 0))) |
| 179 | << "advance" << s.advancePos() |
| 180 | << "insert" << s.isInsertBefore() |
| 181 | << "break" << s.getAttr(&seg, gr_slatBreak, 0); |
| 182 | if (s.just() > 0) |
| 183 | j << "justification" << s.just(); |
| 184 | if (s.getBidiLevel() > 0) |
| 185 | j << "bidi" << s.getBidiLevel(); |
| 186 | if (!s.isBase()) |
| 187 | j << "parent" << json::flat << json::object |
| 188 | << "id" << objectid(dslot(&seg, s.attachedTo())) |
| 189 | << "level" << s.getAttr(0, gr_slatAttLevel, 0) |
| 190 | << "offset" << s.attachOffset() |
| 191 | << json::close; |
| 192 | j << "user" << json::flat << json::array; |
| 193 | for (int n = 0; n!= seg.numAttrs(); ++n) |
| 194 | j << s.userAttrs()[n]; |
| 195 | j << json::close; |
| 196 | if (s.firstChild()) |
| 197 | { |
| 198 | j << "children" << json::flat << json::array; |
| 199 | for (const Slot *c = s.firstChild(); c; c = c->nextSibling()) |
| 200 | j << objectid(dslot(&seg, c)); |
| 201 | j << json::close; |
| 202 | } |
| 203 | if (cslot) |
| 204 | { |
| 205 | // Note: the reason for using Positions to lump together related attributes is to make the |
| 206 | // JSON output slightly more compact. |
| 207 | j << "collision" << json::flat << json::object |
| 208 | // << "shift" << cslot->shift() -- not used pass level, only within the collision routine itself |
| 209 | << "offset" << cslot->offset() |
| 210 | << "limit" << cslot->limit() |
| 211 | << "flags" << cslot->flags() |
| 212 | << "margin" << Position(cslot->margin(), cslot->marginWt()) |
| 213 | << "exclude" << cslot->exclGlyph() |
| 214 | << "excludeoffset" << cslot->exclOffset(); |
| 215 | if (cslot->seqOrder() != 0) |
| 216 | { |
| 217 | j << "seqclass" << Position(cslot->seqClass(), cslot->seqProxClass()) |
| 218 | << "seqorder" << cslot->seqOrder() |
| 219 | << "seqabove" << Position(cslot->seqAboveXoff(), cslot->seqAboveWt()) |
| 220 | << "seqbelow" << Position(cslot->seqBelowXlim(), cslot->seqBelowWt()) |
| 221 | << "seqvalign" << Position(cslot->seqValignHt(), cslot->seqValignWt()); |
| 222 | } |
| 223 | j << json::close; |
| 224 | } |
| 225 | return j << json::close; |
| 226 | } |
| 227 | |
| 228 | |
| 229 | graphite2::objectid::objectid(const dslot & ds) throw() |
| 230 | { |
| 231 | const Slot * const p = ds.second; |
| 232 | uint32 s = uint32(reinterpret_cast<size_t>(p)); |
| 233 | sprintf(name, "%.4x-%.2x-%.4hx" , uint16(s >> 16), uint16(p ? p->userAttrs()[ds.first->silf()->numUser()] : 0), uint16(s)); |
| 234 | name[sizeof name-1] = 0; |
| 235 | } |
| 236 | |
| 237 | graphite2::objectid::objectid(const Segment * const p) throw() |
| 238 | { |
| 239 | uint32 s = uint32(reinterpret_cast<size_t>(p)); |
| 240 | sprintf(name, "%.4x-%.2x-%.4hx" , uint16(s >> 16), 0, uint16(s)); |
| 241 | name[sizeof name-1] = 0; |
| 242 | } |
| 243 | |
| 244 | #endif |
| 245 | |