Advertisement
honey_the_codewitch

Browser support for TinyVG

Jun 8th, 2025
488
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // TinyVG Renderer
  2. // Copyright (C) by honey the codewitch
  3. // MIT License
  4. // To use, pass am ArrayBuffer with the
  5. // TVG document to tvgDimensions
  6. // or tvgRender. With render,
  7. // you place an empty SVG element,
  8. // ex: <svg id="..." xmlms="..." />
  9. // and pass the id of it to the render
  10. // method. At that point, the SVG tag
  11. // will be populated and rendered by
  12. // the browser.
  13.  
  14. const tvgInit = (data, id) => {
  15.     return {
  16.         data: data,
  17.         view: new DataView(data),
  18.         cursor: 0,
  19.         scale: 0,
  20.         color_encoding: 0,
  21.         coord_range: 0,
  22.         width: 0, height: 0,
  23.         colors_size: 0,
  24.         colors: [],
  25.         doc: document.getElementById(id), //SVGSVGElement
  26.         elem: undefined, // SVGElement
  27.         gradIndex: 0
  28.     };
  29. }
  30. const tvgDistance = (pointLhs, pointRhs) => {
  31.     const xd = pointRhs.x - pointLhs.x;
  32.     const yd = pointRhs.y - pointLhs.y;
  33.     return Math.sqrt((xd * xd) + (yd * yd));
  34. }
  35. const tvgAdvCoord = (rangeOrCtx) => {
  36.     let range = rangeOrCtx;
  37.     if (rangeOrCtx.coord_range) {
  38.         range = rangeOrCtx.range;
  39.     }
  40.     switch (range) {
  41.         case 0://"default"
  42.             return 2;
  43.         case 1://"reduced":
  44.             return 1;
  45.         case 2://"extended"
  46.             return 4;
  47.     }
  48. }
  49. const tvgMapZeroToMax = (rangeOrCtx, value) => {
  50.     let range = rangeOrCtx;
  51.     if (rangeOrCtx.coord_range) {
  52.         range = rangeOrCtx.range;
  53.     }
  54.     if (0 == value) {
  55.         switch (range) {
  56.             case 0: //"default"
  57.                 return 0xFFFF;
  58.             case 1: //"reduced"
  59.                 return 0xFF;
  60.             case 2: //"extended"
  61.                 return 0xFFFFFFFF;
  62.         }
  63.         return undefined;
  64.     }
  65.     return value;
  66. }
  67. const tvgReadCoordBI = (range, startIndex, data) => {
  68.     const view = new DataView(data);
  69.     switch (range) {
  70.         case 0: //"default"
  71.             return view.getUint16(startIndex, true);
  72.  
  73.         case 1: //"reduced"
  74.             return view.getUint8(startIndex);
  75.  
  76.         case 2: //"extended"
  77.             return view.getUint32(startIndex, true);
  78.     }
  79.     return undefined;
  80. }
  81. const tvgReadCoord = (ctx) => {
  82.     let result = undefined;
  83.     switch (ctx.coord_range) {
  84.         case 0: //"default"
  85.             result = ctx.view.getUint16(ctx.cursor, true);
  86.             ctx.cursor += 2;
  87.             break;
  88.         case 1: //"reduced"
  89.             result = ctx.view.getUint8(ctx.cursor);
  90.             ctx.cursor += 1;
  91.             break;
  92.         case 2: //"extended"
  93.             result = ctx.view.getUint32(ctx.cursor, true);
  94.             ctx.cursor += 4;
  95.             break;
  96.     }
  97.     return result;
  98. }
  99. const tvgReadU32 = (ctx) => {
  100.     let count = 0;
  101.     let result = 0;
  102.     var byte;
  103.     while (true) {
  104.         byte = ctx.view.getUint8(ctx.cursor++);
  105.         const val = (byte & 0x7F) << (7 * count);
  106.         result |= val;
  107.         if ((byte & 0x80) === 0)
  108.             break;
  109.         ++count;
  110.     }
  111.     return result;
  112. }
  113. const tvgDownscaleCoord = (ctx, coord) => {
  114.     const factor = (1) << ctx.scale;
  115.     return coord / factor;
  116. }
  117. const tvgReadUnit = (ctx) => {
  118.     const val = tvgReadCoord(ctx);
  119.     return tvgDownscaleCoord(ctx, val);
  120. }
  121. const tvgReadPoint = (ctx) => {
  122.     const x = tvgReadUnit(ctx);
  123.     const y = tvgReadUnit(ctx);
  124.     return { x: x, y: y };
  125. }
  126. const tvgReadColor = (ctx) => {
  127.     switch (ctx.color_encoding) {
  128.         case 2: { // TVG_COLOR_F32:
  129.             // read four values
  130.             const data = [];
  131.             data.push(ctx.view.getFloat32(ctx.cursor, true)); ctx.cursor += 4;
  132.             data.push(ctx.view.getFloat32(ctx.cursor, true)); ctx.cursor += 4;
  133.             data.push(ctx.view.getFloat32(ctx.cursor, true)); ctx.cursor += 4;
  134.             data.push(ctx.view.getFloat32(ctx.cursor, true)); ctx.cursor += 4;
  135.             return { r: data[0], g: data[1], b: data[2], a: data[3] };
  136.         }
  137.         case 1: { // TVG_COLOR_U565:
  138.             const data = ctx.view.getUint16(ctx.cursor, true);
  139.             ctx.cursor += 2;
  140.             return {
  141.                 r: (data & 0x1F) / 15.0,
  142.                 g: ((data >>> 5) & 0x3F) / 31.0,
  143.                 b: ((data >>> 11) & 0x1F) / 15.0,
  144.                 a: 1.0
  145.             };
  146.         }
  147.         case 0: { // TVG_COLOR_U8888:
  148.             // read four values
  149.             const data = [];
  150.             data.push(ctx.view.getUint8(ctx.cursor++));
  151.             data.push(ctx.view.getUint8(ctx.cursor++));
  152.             data.push(ctx.view.getUint8(ctx.cursor++));
  153.             data.push(ctx.view.getUint8(ctx.cursor++));
  154.             return { r: data[0] / 255.0, g: data[1] / 255.0, b: data[2] / 255.0, a: data[3] / 255.0 };
  155.         }
  156.         case 3: // TVG_COLOR_CUSTOM
  157.             throw "TinyVG: Custom color table not supported";
  158.         default:
  159.             throw "TinyVG: Invalid color format";
  160.     }
  161. }
  162. const tvgParseGradient = (ctx) => {
  163.     const point0 = tvgReadPoint(ctx);
  164.     const point1 = tvgReadPoint(ctx);
  165.     const color0 = tvgReadU32(ctx);
  166.     const color1 = tvgReadU32(ctx);
  167.     return { point0: point0, point1: point1, color0: color0, color1: color1 };
  168. }
  169. const tvgParseStyle = (ctx, kind) => {
  170.     switch (kind) {
  171.         case 0: // TVG_STYLE_FLAT:
  172.             return { kind: kind, flat: tvgReadU32(ctx) };
  173.         case 1: // TVG_STYLE_LINEAR:
  174.             return { kind: kind, linear: tvgParseGradient(ctx) };
  175.         case 2: //TVG_STYLE_RADIAL:
  176.             return { kind: kind, radial: tvgParseGradient(ctx) };
  177.         default:
  178.             throw "TinyVG: Invalid format parsing style";
  179.     }
  180. }
  181. const tvgParseFillHeader = (ctx, kind) => {
  182.     const u32 = tvgReadU32(ctx);
  183.     const size = u32 + 1;
  184.     //out_header->size = count;
  185.     const style = tvgParseStyle(ctx, kind);
  186.     return { size: size, style: style };
  187. }
  188. const tvgParseLineHeader = (ctx, kind) => {
  189.     const u32 = tvgReadU32(ctx);
  190.     const size = u32 + 1;
  191.  
  192.     const style = tvgParseStyle(ctx, kind);
  193.     const line_width = tvgReadUnit(ctx);
  194.  
  195.     return { size: size, style: style, line_width: line_width };
  196. }
  197.  
  198. const tvgParseLineFillHeader = (ctx, kind) => {
  199.  
  200.     var d = ctx.view.getUint8(ctx.cursor++);
  201.     const size = (d & 0x3F) + 1;
  202.     const fill_style = tvgParseStyle(ctx, kind);
  203.     const line_style = tvgParseStyle(ctx, (d >>> 6) & 0x03);
  204.     const line_width = tvgReadUnit(ctx);
  205.     return { size: size, fill_style: fill_style, line_style: line_style, line_width: line_width };
  206. }
  207. const tvgParsePathD = (ctx, size) => {
  208.     var st, cur;
  209.     var pt;
  210.     var u32;
  211.     var f32;
  212.     var d;
  213.     let result = "";
  214.     pt = tvgReadPoint(ctx);
  215.     result += `M${pt.x} ${pt.y}`;
  216.     st = pt;
  217.     cur = pt;
  218.     for (let j = 0; j < size; ++j) {
  219.         d = ctx.view.getUint8(ctx.cursor++);
  220.         if (((d >>> 4) & 1) !== 0) { // has line
  221.             tvgReadUnit(ctx); // throw away line width (future use)
  222.         }
  223.         switch (d & 7) {
  224.             case 0: // TVG_PATH_LINE:
  225.                 pt = tvgReadPoint(ctx);
  226.                 result += ` L${pt.x} ${pt.y}`
  227.                 cur = pt;
  228.                 break;
  229.             case 1: // TVG_PATH_HLINE:
  230.                 pt.x = tvgReadUnit(ctx);;
  231.                 pt.y = cur.y;
  232.                 result += ` H${pt.x}`;
  233.                 cur = pt;
  234.                 break;
  235.             case 2: // TVG_PATH_VLINE:
  236.                 pt.x = cur.x;
  237.                 pt.y = tvgReadUnit(ctx);
  238.                 result += ` V${pt.y}`;
  239.                 cur = pt;
  240.                 break;
  241.             case 3: { // TVG_PATH_CUBIC:
  242.                 const ctrl1 = tvgReadPoint(ctx);
  243.                 const ctrl2 = tvgReadPoint(ctx);
  244.                 const endp = tvgReadPoint(ctx);
  245.                 result += ` C${ctrl1.x} ${ctrl1.y} ${ctrl2.x} ${ctrl2.y} ${endp.x} ${endp.y}`;
  246.                 cur = endp;
  247.             } break;
  248.             case 4: { // TVG_PATH_ARC_CIRCLE: {
  249.                 d = ctx.view.getUint8(ctx.cursor++);
  250.                 const radius = tvgReadUnit(ctx);
  251.                 pt = tvgReadPoint(ctx);
  252.                 result += ` A${radius} ${radius} 0 ${d & 1} ${1 - ((d >>> 1) & 1)} ${pt.x} ${pt.y}`;
  253.                 cur = pt;
  254.             } break;
  255.             case 5: { // TVG_PATH_ARC_ELLIPSE:
  256.                 d = ctx.view.getUint8(ctx.cursor++);
  257.                 const radius_x = tvgReadUnit(ctx);
  258.                 const radius_y = tvgReadUnit(ctx);
  259.                 const rotation = tvgReadUnit(ctx);
  260.                 pt = tvgReadPoint(ctx);
  261.                 result += ` A${radius_x} ${radius_y} ${rotation} ${d & 1} ${1 - ((d >>> 1) & 1)} ${pt.x} ${pt.y}`;
  262.                 cur = pt;
  263.             } break;
  264.             case 6: // TVG_PATH_CLOSE:
  265.                 result += ' Z';
  266.                 cur = st;
  267.                 break;
  268.             case 7: { // TVG_PATH_QUAD:
  269.                 const ctrl = tvgReadPoint(ctx);
  270.                 const endp = tvgReadPoint(ctx);
  271.                 result += ` Q${ctrl.x} ${ctrl.y} ${endp.x} ${endp.y}`
  272.                 cur = endp;
  273.             } break;
  274.             default:
  275.                 throw "TinyVG: Unrecognized command parsing path";
  276.         }
  277.     }
  278.     return result;
  279. }
  280. const tvgParseRect = (ctx) => {
  281.     const pt = tvgReadPoint(ctx);
  282.     const w = tvgReadUnit(ctx);
  283.     const h = tvgReadUnit(ctx);
  284.     return { x: pt.x, y: pt.y, width: w, height: h };
  285. }
  286. const tvgToHex = (code) => {
  287.     let result = code.toString(16);
  288.     if (result.length === 1) {
  289.         return "0" + result;
  290.     }
  291.     return result;
  292. }
  293. const tvgColorToSvgColorAndOpacity = (col) => {
  294.     return { color: `#${tvgToHex(col.r * 255)}${tvgToHex(col.g * 255)}${tvgToHex(col.b * 255)}`, opacity: col.a };
  295. }
  296. const tvgCreateSvgNode = (n, v) => {
  297.     n = document.createElementNS("http://www.w3.org/2000/svg", n);
  298.     if (v) {
  299.         for (let p in v) {
  300.             n.setAttributeNS(null, p.replace(/[A-Z]/g, function (m, p, o, s) { return "-" + m.toLowerCase(); }), v[p]);
  301.         }
  302.     }
  303.     return n;
  304. }
  305. const tvgAddSvgAttribute = (n, a, v) => {
  306.     n.setAttributeNS(null, a, v);
  307. }
  308. const tvgCreateSvgGradient = (ctx, style) => {
  309.     let da = ctx.doc.getElementsByTagNameNS("http://www.w3.org/2000/svg", "defs");
  310.     var defs;
  311.     if (da.length == 0) {
  312.         defs = tvgCreateSvgNode("defs");
  313.         ctx.doc.prepend(defs);
  314.     } else {
  315.         defs = da[0];
  316.     }
  317.     if (style.kind === 1) {
  318.         const node = tvgCreateSvgNode("linearGradient",
  319.             {
  320.                 id: `TvgGradient${ctx.gradIndex + 1}`,
  321.                 x1: style.linear.point0.x,
  322.                 y1: style.linear.point0.y,
  323.                 x2: style.linear.point1.x,
  324.                 y2: style.linear.point1.y
  325.             });
  326.         node.setAttributeNS(null, "gradientUnits", "userSpaceOnUse");
  327.         node.setAttributeNS(null, "spreadMethod", "pad");
  328.         let col = tvgColorToSvgColorAndOpacity(ctx.colors[style.linear.color0]);
  329.         const stop1 = tvgCreateSvgNode("stop", { offset: "0%", stopColor: col.color });//, stopOpacity: col.opacity});
  330.         node.appendChild(stop1);
  331.         col = tvgColorToSvgColorAndOpacity(ctx.colors[style.linear.color1]);
  332.         const stop2 = tvgCreateSvgNode("stop", { offset: "100%", stopColor: col.color });//, stopOpacity: col.opacity});
  333.         node.appendChild(stop2);
  334.         defs.appendChild(node);
  335.         ++ctx.gradIndex;
  336.         return node.getAttributeNS(null, "id");
  337.     } else if (style.kind === 2) {
  338.         const r = tvgDistance(style.radial.point0, style.radial.point1);
  339.         const node = tvgCreateSvgNode("radialGradient",
  340.             {
  341.                 id: `TvgGradient${ctx.gradIndex + 1}`,
  342.                 cx: style.radial.point0.x,
  343.                 cy: style.radial.point0.y,
  344.                 fx: style.radial.point0.x,
  345.                 fy: style.radial.point0.y,
  346.                 r: r
  347.             });
  348.         node.setAttributeNS(null, "gradientUnits", "userSpaceOnUse");
  349.         node.setAttributeNS(null, "spreadMethod", "pad");
  350.         let col = tvgColorToSvgColorAndOpacity(ctx.colors[style.radial.color0]);
  351.         const stop1 = tvgCreateSvgNode("stop", { offset: "0%", stopColor: col.color, stopOpacity: col.opacity });
  352.         node.appendChild(stop1);
  353.         col = tvgColorToSvgColorAndOpacity(ctx.colors[style.radial.color1]);
  354.         const stop2 = tvgCreateSvgNode("stop", { offset: "100%", stopColor: col.color, stopOpacity: col.opacity });
  355.         node.appendChild(stop2);
  356.         defs.appendChild(node);
  357.         ++ctx.gradIndex;
  358.         return node.getAttributeNS(null, "id");
  359.     } else if (style.kind === 0) throw "TinyVG: attempt to pass flat style to create gradient";
  360.     else throw "TinyVG: attempt to pass an invalid style to create gradient";
  361. }
  362. const tvgApplyStyle = (ctx, style, isFill) => {
  363.     if (style.kind === 0) { // flat
  364.         const col = tvgColorToSvgColorAndOpacity(ctx.colors[style.flat]);
  365.         if (isFill) {
  366.             tvgAddSvgAttribute(ctx.elem, "fill", col.color);
  367.             tvgAddSvgAttribute(ctx.elem, "fill-opacity", col.opacity);
  368.         } else {
  369.             tvgAddSvgAttribute(ctx.elem, "stroke", col.color);
  370.             tvgAddSvgAttribute(ctx.elem, "stroke-opacity", col.opacity);
  371.         }
  372.     } else if (style.kind === 1 || style.kind === 2) { // linear
  373.         const grad = tvgCreateSvgGradient(ctx, style);
  374.         if (isFill) {
  375.             tvgAddSvgAttribute(ctx.elem, "fill", `url(#${grad})`);
  376.         } else {
  377.             tvgAddSvgAttribute(ctx.elem, "stroke", `url(#${grad})`);
  378.         }
  379.     } else throw "TinyVG: attempt to apply invalid style";
  380. }
  381. const tvgParseFillRectangles = (ctx, size, fill_style) => {
  382.     let count = size;
  383.     if (count === 0) throw "TinyVG: Invalid zero length filled rectangles entry";
  384.     let rect = tvgParseRect(ctx);
  385.     let r = tvgCreateSvgNode("rect", rect);
  386.     ctx.doc.appendChild(r);
  387.     ctx.elem = r;
  388.     tvgAddSvgAttribute(ctx.elem, "fill-rule", "evenodd");
  389.     tvgApplyStyle(ctx, fill_style, true);
  390.     const attrs = {};
  391.     attrs.fillRule = "evenodd";
  392.     if (fill_style.kind !== 0) {
  393.         attrs.fill = r.getAttributeNS(null, "fill")
  394.     } else {
  395.         attrs.fill = r.getAttributeNS(null, "fill")
  396.         attrs.fillOpacity = r.getAttributeNS(null, "fill-opacity");
  397.     }
  398.     --count;
  399.     while (count--) {
  400.         rect = tvgParseRect(ctx);
  401.         const localAttrs = { ...attrs, ...rect };
  402.         r = tvgCreateSvgNode("rect", localAttrs);
  403.         ctx.doc.appendChild(r);
  404.         ctx.elem = r;
  405.     }
  406. }
  407. const tvgParseLineFillRectangles = (ctx, size, fill_style, line_style, line_width) => {
  408.     let count = size;
  409.     if (count === 0) throw "TinyVG: Invalid zero length line filled rectangles entry";
  410.     if (line_width === 0) {  // 0 width is invalid
  411.         line_width = .001;
  412.     }
  413.     let rect = tvgParseRect(ctx);
  414.     let r = tvgCreateSvgNode("rect", rect);
  415.     ctx.doc.appendChild(r);
  416.     ctx.elem = r;
  417.     tvgAddSvgAttribute(ctx.elem, "fill-rule", "evenodd");
  418.     tvgAddSvgAttribute(ctx.elem, "stroke-width", line_width);
  419.     tvgApplyStyle(ctx, fill_style, true);
  420.     tvgApplyStyle(ctx, line_style, false);
  421.     const attrs = {};
  422.     attrs.fillRule = "evenodd";
  423.     if (fill_style.kind !== 0) {
  424.         attrs.fill = r.getAttributeNS(null, "fill");
  425.     } else {
  426.         attrs.fill = r.getAttributeNS(null, "fill");
  427.         attrs.fillOpacity = r.getAttributeNS(null, "fill-opacity");
  428.     }
  429.     if (line_style.kind !== 0) {
  430.         attrs.stroke = r.getAttributeNS(null, "stroke");
  431.     } else {
  432.         attrs.stroke = r.getAttributeNS(null, "stroke");
  433.         attrs.strokeOpacity = r.getAttributeNS(null, "stroke-opacity");
  434.     }
  435.     attrs.strokeWidth = line_width;
  436.     --count;
  437.     while (count--) {
  438.         rect = tvgParseRect(ctx);
  439.         const localAttrs = { ...attrs, ...rect };
  440.         r = tvgCreateSvgNode("rect", localAttrs);
  441.         ctx.doc.appendChild(r);
  442.         ctx.elem = r;
  443.     }
  444. }
  445. const tvgParseFillPaths = (ctx, size, style) => {
  446.     if (size === 0) throw "TinyVG: Invalid zero filled paths entry";
  447.     const attrs = {};
  448.     attrs.fillRule = "evenodd";
  449.     attrs.strokeOpacity = 0;
  450.     attrs.strokeWidth = 0;
  451.     const sizes = [];
  452.     for (let i = 0; i < size; ++i) {
  453.         sizes.push(tvgReadU32(ctx) + 1);
  454.     }
  455.     let p = tvgCreateSvgNode("path", attrs);
  456.     ctx.doc.appendChild(p);
  457.     ctx.elem = p;
  458.     tvgApplyStyle(ctx, style, true);
  459.     if (style.kind !== 0) {
  460.         attrs.fill = p.getAttributeNS(null, "fill");
  461.     } else {
  462.         attrs.fill = p.getAttributeNS(null, "fill");
  463.         attrs.fillOpacity = p.getAttributeNS(null, "fill-opacity");
  464.     }
  465.     let d = tvgParsePathD(ctx, sizes[0]);
  466.     for (let i = 1; i < size; ++i) {
  467.         d+= ` ${tvgParsePathD(ctx, sizes[i])}`;
  468.     }
  469.     tvgAddSvgAttribute(p, "d", d);
  470. }
  471. const tvgParseLinePaths = (ctx, size, line_style, line_width) => {
  472.     if (size === 0) throw "TinyVG: Invalid zero line paths entry";
  473.     if (line_width === 0) {  // 0 width is invalid
  474.         line_width = .001;
  475.     }
  476.     const attrs = {};
  477.     const sizes = [];
  478.     for (let i = 0; i < size; ++i) {
  479.         sizes.push(tvgReadU32(ctx) + 1);
  480.     }
  481.     let p = tvgCreateSvgNode("path", attrs);
  482.     ctx.doc.appendChild(p);
  483.     ctx.elem = p;
  484.     tvgAddSvgAttribute(ctx.elem, "fill-opacity", 0);
  485.     tvgAddSvgAttribute(ctx.elem, "stroke-width", line_width);
  486.     tvgApplyStyle(ctx, line_style, false);
  487.     if (line_style.kind !== 0) {
  488.         attrs.stroke = p.getAttributeNS(null, "stroke");
  489.     } else {
  490.         attrs.stroke = p.getAttributeNS(null, "stroke");
  491.         attrs.strokeOpacity = p.getAttributeNS(null, "stroke-opacity");
  492.     }
  493.     attrs.strokeWidth = line_width;
  494.     attrs.fillOpacity = 0;
  495.     let d = tvgParsePathD(ctx, sizes[0]);
  496.     for (let i = 1; i < size; ++i) {
  497.         d+= ` ${tvgParsePathD(ctx, sizes[i])}`;
  498.     }
  499.     tvgAddSvgAttribute(p, "d", d);
  500. }
  501. const tvgParseLineFillPaths = (ctx, size, fill_style, line_style, line_width) => {
  502.     if (size === 0) throw "TinyVG: Invalid zero line filled paths entry";
  503.     if (line_width === 0) {  // 0 width is invalid
  504.         line_width = .001;
  505.     }
  506.     const attrs = {};
  507.     attrs.fillRule = "evenodd";
  508.     const sizes = [];
  509.     for (let i = 0; i < size; ++i) {
  510.         sizes.push(tvgReadU32(ctx) + 1);
  511.     }
  512.     let p = tvgCreateSvgNode("path", attrs);
  513.     ctx.doc.appendChild(p);
  514.     ctx.elem = p;
  515.     tvgApplyStyle(ctx, fill_style, true);
  516.     if (fill_style.kind !== 0) {
  517.         attrs.fill = p.getAttributeNS(null, "fill");
  518.     } else {
  519.         attrs.fill = p.getAttributeNS(null, "fill");
  520.         attrs.fillOpacity = p.getAttributeNS(null, "fill-opacity");
  521.     }
  522.     tvgApplyStyle(ctx, line_style, false);
  523.     if (line_style.kind !== 0) {
  524.         attrs.stroke = p.getAttributeNS(null, "stroke");
  525.     } else {
  526.         attrs.stroke = p.getAttributeNS(null, "stroke");
  527.         attrs.strokeOpacity = p.getAttributeNS(null, "stroke-opacity");
  528.     }
  529.     attrs.strokeWidth = line_width;
  530.     tvgAddSvgAttribute(p, "stroke-width", line_width);
  531.     let d = tvgParsePathD(ctx, sizes[0]);
  532.     for (let i = 1; i < size; ++i) {
  533.         d+= ` ${tvgParsePathD(ctx, sizes[i])}`;
  534.     }
  535.     tvgAddSvgAttribute(p, "d", d);
  536. }
  537. const tvgParseFillPolygon = (ctx, size, fill_style) => {
  538.     if (size === 0) throw "TinyVG: Invalid zero polygon entry";
  539.     let count = size;
  540.     let points = "";
  541.     let pt = tvgReadPoint(ctx);
  542.     points += `${pt.x},${pt.y}`;
  543.     while (--count) {
  544.         pt = tvgReadPoint(ctx);
  545.         points += ` ${pt.x},${pt.y}`;
  546.     }
  547.     const attrs = { fillRule: "evenodd", points: points };
  548.     let p = tvgCreateSvgNode("polygon", attrs);
  549.     ctx.doc.appendChild(p);
  550.     ctx.elem = p;
  551.     tvgApplyStyle(ctx, fill_style, true);
  552. }
  553. const tvgParsePolyline = (ctx, size, line_style, line_width, close) => {
  554.     if (size === 0) throw "TinyVG: Invalid zero polyline entry";
  555.     if (line_width === 0) {  // 0 width is invalid
  556.         line_width = .001;
  557.     }
  558.     let count = size;
  559.     let points = "";
  560.     let pt = tvgReadPoint(ctx);
  561.     points += `${pt.x},${pt.y}`;
  562.     while (--count) {
  563.         pt = tvgReadPoint(ctx);
  564.         points += ` ${pt.x},${pt.y}`;
  565.     }
  566.     const attrs = { points: points, lineWidth: line_width, fillOpacity: 0 };
  567.     let p = tvgCreateSvgNode(close ? "polygon" : "polyline", attrs);
  568.     ctx.doc.appendChild(p);
  569.     ctx.elem = p;
  570.     tvgApplyStyle(ctx, line_style, false);
  571. }
  572. const tvgParseLineFillPolyline = (ctx, size, fill_style, line_style, line_width, close) => {
  573.     if (size === 0) throw "TinyVG: Invalid zero line fill polyline entry";
  574.     if (line_width === 0) {  // 0 width is invalid
  575.         line_width = .001;
  576.     }
  577.     let count = size;
  578.     let points = "";
  579.     let pt = tvgReadPoint(ctx);
  580.     points += `${pt.x},${pt.y}`;
  581.     while (--count) {
  582.         pt = tvgReadPoint(ctx);
  583.         points += ` ${pt.x},${pt.y}`;
  584.     }
  585.     const attrs = { points: points, lineWidth: line_width, fillRule: "evenodd" };
  586.     let p = tvgCreateSvgNode(close ? "polygon" : "polyline", attrs);
  587.     ctx.doc.appendChild(p);
  588.     ctx.elem = p;
  589.     tvgApplyStyle(ctx, fill_style, true);
  590.     tvgApplyStyle(ctx, line_style, false);
  591. }
  592. const tvgParseLines = (ctx, size, line_style, line_width) => {
  593.     if (size === 0) throw "TinyVG: Invalid zero lines entry";
  594.     for (let i = 0; i < size; ++i) {
  595.         const pt1 = tvgReadPoint(ctx);
  596.         const pt2 = tvgReadPoint(ctx);
  597.         const attrs = { x1: pt1.x, y1: pt1.y, x2: pt2.x, y2: pt2.y, strokeWidth: line_width };
  598.         let l = tvgCreateSvgNode("line", attrs);
  599.         ctx.doc.appendChild(l);
  600.         ctx.elem = l;
  601.         tvgApplyStyle(ctx, line_style, false);
  602.     }
  603. }
  604. const tvgParseCommands = (ctx) => {
  605.     let cmd = 255;
  606.     while (cmd != 0) {
  607.         cmd = ctx.view.getUint8(ctx.cursor++);
  608.         switch (cmd & 0x3F) {
  609.             case 0: // TVG_CMD_END_DOCUMENT:
  610.                 // console.log("TVG END");
  611.                 break;
  612.             case 1: { // TVG_CMD_FILL_POLYGON:
  613.                 // console.log("TVG FILL POLYGON");
  614.                 const data = tvgParseFillHeader(ctx, (cmd >>> 6) & 3);
  615.                 tvgParseFillPolygon(ctx, data.size, data.style);
  616.             } break;
  617.             case 2: { // TVG_CMD_FILL_RECTANGLES:
  618.                 // console.log("TVG FILL RECTANGLES");
  619.                 const data = tvgParseFillHeader(ctx, (cmd >>> 6) & 3);
  620.                 tvgParseFillRectangles(ctx, data.size, data.style);
  621.             } break;
  622.             case 3: { // TVG_CMD_FILL_PATH:
  623.                 // console.log("TVG FILL PATH");
  624.                 const data = tvgParseFillHeader(ctx, (cmd >>> 6) & 3);
  625.                 tvgParseFillPaths(ctx, data.size, data.style);
  626.             } break;
  627.             case 4: { // TVG_CMD_DRAW_LINES:
  628.                 // console.log("TVG LINES");
  629.                 const data = tvgParseLineHeader(ctx, (cmd >>> 6) & 3);
  630.                 tvgParseLines(ctx, data.size, data.style, data.line_width);
  631.             } break;
  632.             case 5: { // TVG_CMD_DRAW_LINE_LOOP:
  633.                 // console.log("TVG LINE LOOP");
  634.                 const data = tvgParseLineHeader(ctx, (cmd >>> 6) & 3);
  635.                 tvgParsePolyline(ctx, data.size, data.style, data.line_width, true);
  636.             } break;
  637.             case 6: { // TVG_CMD_DRAW_LINE_STRIP:
  638.                 // console.log("TVG LINE STRIP");
  639.                 const data = tvgParseLineHeader(ctx, (cmd >>> 6) & 3);
  640.                 tvgParsePolyline(ctx, data.size, data.style, data.line_width, false);
  641.             } break;
  642.             case 7: { // TVG_CMD_DRAW_LINE_PATH:
  643.                 // console.log("TVG LINE PATH");
  644.                 const data = tvgParseLineHeader(ctx, (cmd >>> 6) & 3);
  645.                 tvgParseLinePaths(ctx, data.size, data.style, data.line_width);
  646.             } break;
  647.             case 8: { // TVG_CMD_OUTLINE_FILL_POLYGON:
  648.                 // console.log("TVG OUTLINE FILL POLYGON");
  649.                 const data = tvgParseLineFillHeader(ctx, (cmd >>> 6) & 3);
  650.                 tvgParseLineFillPolyline(ctx, data.size, data.fill_style, data.line_style, data.line_width, true);
  651.             } break;
  652.             case 9: { // TVG_CMD_OUTLINE_FILL_RECTANGLES:
  653.                 // console.log("TVG OUTLINE FILL RECTANGLES");
  654.                 const data = tvgParseLineFillHeader(ctx, (cmd >>> 6) & 3);
  655.                 tvgParseLineFillRectangles(ctx, data.size, data.fill_style, data.line_style, data.line_width);
  656.             } break;
  657.             case 10: { // TVG_CMD_OUTLINE_FILL_PATH:
  658.                 // console.log("TVG OUTLINE FILL PATH");
  659.                 const data = tvgParseLineFillHeader(ctx, (cmd >>> 6) & 3);
  660.                 tvgParseLineFillPaths(ctx, data.size, data.fill_style, data.line_style, data.line_width);
  661.             } break;
  662.             default:
  663.                 throw `TinyVG: Invalid command in document (0x${tvgToHex(cmd)})`;
  664.         }
  665.     }
  666. }
  667. // get the {width, height} of a TVG in an arraybuffer
  668. export const tvgDimensions = (data) => {
  669.     if (data) {
  670.         const view = new DataView(data);
  671.         if (view.byteLength > 5) {
  672.             // check for TVG v 1.0 header
  673.             if (view.getUint8(0) == 0x72 && view.getUint8(1) == 0x56 && view.getUint8(2) == 1) {
  674.                 const flags = view.getUint8(3);
  675.                 const range = (flags >>> 6) & 0x03;
  676.                 const w = tvgReadCoordBI(range, 4, data);
  677.                 const h = tvgReadCoordBI(range, 4 + tvgAdvCoord(range), data);
  678.                 const dim = {
  679.                     width: tvgMapZeroToMax(range, w),
  680.                     height: tvgMapZeroToMax(range, h)
  681.                 };
  682.                 return dim;
  683.             }
  684.         }
  685.     }
  686.     return undefined;
  687. }
  688. // Render a TVG in an arraybuffer (data) to an SVG tag indicated by the id
  689. export const tvgRender = (id, data) => {
  690.     if (!id) throw "TinyVG: Must specify the id of an SVG element";
  691.     if (!data) throw "TinyVG: Must provide an ArrayBuffer with TVG data";
  692.     const view = new DataView(data);
  693.     if (view.byteLength > 5) {
  694.         if (view.getUint8(0) == 0x72 && view.getUint8(1) == 0x56 && view.getUint8(2) == 1) {
  695.             const ctx = tvgInit(data, id);
  696.             if (ctx.doc) {
  697.                 const flags = view.getUint8(3);
  698.                 ctx.scale = (flags & 0xF);
  699.                 ctx.color_encoding = ((flags >>> 4) & 0x3);
  700.                 ctx.coord_range = (flags >>> 6) & 0x03;
  701.                 ctx.cursor = 4;
  702.                 const w = tvgReadCoord(ctx);
  703.                 const h = tvgReadCoord(ctx);
  704.                 ctx.width = tvgMapZeroToMax(ctx, w);
  705.                 ctx.height = tvgMapZeroToMax(ctx, h);
  706.                 const colcount = tvgReadU32(ctx);
  707.                 if (!colcount || colcount === 0) throw "TinyVG: invalid format - color table contains nothing";
  708.                 for (let i = 0; i < colcount; ++i) {
  709.                     ctx.colors.push(tvgReadColor(ctx));
  710.                 }
  711.                 while (ctx.doc.firstChild) {
  712.                     ctx.doc.removeChild(ctx.doc.lastChild);
  713.                 }
  714.                 tvgAddSvgAttribute(ctx.doc, "width", w.toString(10));
  715.                 tvgAddSvgAttribute(ctx.doc, "height", h.toString(10));
  716.                 tvgAddSvgAttribute(ctx.doc, "viewBox", `0 0 ${w} ${h}`);
  717.                 tvgParseCommands(ctx);
  718.                 return;
  719.             }
  720.         }
  721.     }
  722.     throw "TinyVG: Not a valid TinyVG file";
  723. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement