diff --git a/bandwidth/CTA861.txt b/bandwidth/CTA861.txt new file mode 100644 index 0000000..739ab9c --- /dev/null +++ b/bandwidth/CTA861.txt @@ -0,0 +1,163 @@ +Rev,VIC,Alt,H,V,Scan,H tot,H bl,V tot,V bl,H freq (kHz),V freq (Hz),Px CLK,HF,HS,HB,VF,VS,VB,Hpol,Vpol +A,1,,640,480,p,800,160,525,45,31.469,59.94,25.175,16,96,48,10,2,33,n,n +A,1,,640,480,p,800,160,525,45,31.5,60,25.2,16,96,48,10,2,33,n,n +A,2,3,720,480,p,858,138,525,45,31.469,59.94,27,16,62,60,8,6,31,n,n +A,2,3,720,480,p,858,138,525,45,31.5,60,27.027,16,62,60,8,6,31,p,p +A,4,69,1280,720,p,1650,370,750,30,44.955,59.939,74.175,110,40,220,5,5,20,p,p +A,4,69,1280,720,p,1650,370,750,30,45,60,74.25,110,40,220,5,5,20,p,p +A,5,,1920,1080,i,2200,280,1125,22.5,33.716,59.939,74.175,88,44,148,2,5,15,p,p +A,5,,1920,1080,i,2200,280,1125,22.5,33.75,60,74.25,88,44,148,2,5,15,p,p +A,6,7,1440,480,i,1716,276,525,22.5,15.734,59.94,27,38,124,114,4,3,15,n,n +A,6,7,1440,480,i,1716,276,525,22.5,15.75,60,27.027,38,124,114,4,3,15,n,n +B,8,9,1440,240,p,1716,276,262,22,15.734,60.054,27,38,124,114,4,3,15,n,n +B,8,9,1440,240,p,1716,276,263,23,15.734,59.826,27,38,124,114,5,3,15,n,n +B,10,11,2880,480,i,3432,552,525,22.5,15.734,59.94,54,76,248,228,4,3,15,n,n +B,12,13,2880,240,p,3432,552,525,22,15.734,60.054,54,76,248,228,4,3,15,n,n +B,12,13,2880,240,p,3432,552,525,23,15.734,59.826,54,76,248,228,5,3,15,n,n +B,14,15,1440,480,p,1716,276,525,45,31.469,59.94,54,32,124,120,9,6,30,n,n +B,14,15,1440,480,p,1716,276,525,45,31.5,60,54.054,32,124,120,9,6,30,n,n +B,16,76,1920,1080,p,2200,280,1125,45,67.432,59.939,148.35,88,44,148,4,5,36,p,p +B,16,76,1920,1080,p,2200,280,1125,45,67.5,60,148.5,88,44,148,4,5,36,p,p +A,17,18,720,576,p,864,144,625,49,31.25,50,27,12,64,68,5,5,39,n,n +A,19,68,1280,720,p,1980,700,750,30,37.5,50,74.25,440,40,220,5,5,20,p,p +A,20,,1920,1080,i,2640,720,1125,22.5,28.125,50,74.25,528,44,148,2,5,15,p,p +A,21,22,1440,576,i,1728,288,625,24.5,15.625,50,27,24,126,138,2,3,19,n,n +B,23,24,1440,288,p,1728,288,312,24,15.625,50.08,27,24,126,138,2,3,19,n,n +B,23,24,1440,288,p,1728,288,313,25,15.625,49.92,27,24,126,138,3,3,19,n,n +B,23,24,1440,288,p,1728,288,314,26,15.625,49.761,27,24,126,138,4,3,19,n,n +B,25,26,2880,576,i,3456,576,625,24.5,15.625,50,54,48,252,276,2,3,19,n,n +B,27,28,2880,288,p,3456,576,312,24,15.625,50.08,54,48,252,276,2,3,19,n,n +B,27,28,2880,288,p,3456,576,313,25,15.625,49.92,54,48,252,276,3,3,19,n,n +B,27,28,2880,288,p,3456,576,314,26,15.625,49.761,54,48,252,276,4,3,19,n,n +B,29,30,1440,576,p,1728,288,625,49,31.25,50,54,24,128,136,5,5,39,n,n +B,31,75,1920,1080,p,2640,720,1125,45,56.25,50,148.5,528,44,148,4,5,36,p,p +B,32,72,1920,1080,p,2750,830,1125,45,26.973,23.976,74.175,638,44,148,4,5,36,p,p +B,32,72,1920,1080,p,2750,830,1125,45,27,24,74.25,638,44,148,4,5,36,p,p +B,33,73,1920,1080,p,2640,720,1125,45,28.125,25,74.25,528,44,148,4,5,36,p,p +B,34,74,1920,1080,p,2200,280,1125,45,33.716,29.97,74.175,88,44,148,4,5,36,p,p +B,34,74,1920,1080,p,2200,280,1125,45,33.75,30,74.25,88,44,148,4,5,36,p,p +C,35,36,2880,480,p,3432,552,525,45,31.5,60,108.108,64,248,240,9,6,30,n,n +C,37,38,2880,576,p,3456,576,625,49,31.25,50,108,48,256,272,5,5,39,n,n +C,39,,1920,1080,i,2304,384,1250,85,31.25,50,72,32,168,184,23,5,57,p,n +C,40,,1920,1080,i,2640,720,1125,22.5,56.25,100,148.5,528,44,148,2,5,15,p,p +C,41,,1280,720,p,1980,700,750,30,75,100,148.5,440,40,220,5,5,20,p,p +C,42,43,720,576,p,864,144,625,49,62.5,100,54,12,64,68,5,5,39,n,n +C,44,45,1440,576,i,1728,288,625,24.5,31.25,100,54,24,126,138,2,3,19,n,n +C,46,,1920,1080,i,2200,280,1125,22.5,67.432,119.88,148.352,88,44,148,2,5,15,p,p +C,46,,1920,1080,i,2200,280,1125,22.5,67.5,120,148.5,88,44,148,2,5,15,p,p +C,47,,1280,720,p,1650,370,750,30,89.909,119.88,148.352,110,40,220,5,5,20,p,p +C,47,,1280,720,p,1650,370,750,30,90,120,148.5,110,40,220,5,5,20,p,p +C,48,49,720,480,p,858,138,525,45,62.937,119.88,54,16,62,60,9,6,30,n,n +C,48,49,720,480,p,858,138,525,45,63,120,54.054,16,62,60,9,6,30,n,n +C,50,51,1440,480,i,1716,276,525,22.5,31.469,119.88,54,38,124,114,4,3,15,n,n +C,50,51,1440,480,i,1716,276,525,22.5,31.5,120,54.054,38,124,114,4,3,15,p,p +C,52,53,720,576,p,864,144,625,49,125,200,108,12,64,68,5,5,39,n,n +C,54,55,1440,576,i,1728,288,625,24.5,62.5,200,108,24,126,138,2,3,19,n,n +C,56,57,720,480,p,858,138,525,45,125.874,239.76,108,16,62,60,9,6,30,n,n +C,56,57,720,480,p,858,138,525,45,126,240,108.108,16,62,60,9,6,30,n,n +C,58,59,1440,480,i,1716,276,525,22.5,62.937,239.76,108,38,124,114,4,3,15,n,n +C,58,59,1440,480,i,1716,276,525,22.5,63,240,108.108,38,124,114,4,3,15,n,n +E,60,65,1280,720,p,3300,2020,750,30,17.982,23.976,59.341,1760,40,220,5,5,20,p,p +E,60,65,1280,720,p,3300,2020,750,30,18,24,59.4,1760,40,220,5,5,20,p,p +E,61,66,1280,720,p,3960,2680,750,30,18.75,25,74.25,2420,40,220,5,5,20,p,p +E,62,67,1280,720,p,3300,2020,750,30,22.478,29.97,74.176,1760,40,220,5,5,20,p,p +E,62,67,1280,720,p,3300,2020,750,30,22.5,30,74.25,1760,40,220,5,5,20,p,p +E,63,78,1920,1080,p,2200,280,1125,45,134.865,119.88,296.703,88,44,148,4,5,36,p,p +E,63,78,1920,1080,p,2200,280,1125,45,135,120,297,88,44,148,4,5,36,p,p +E,64,77,1920,1080,p,2640,720,1125,45,112.5,100,297,528,44,148,4,5,36,p,p +F,79,,1680,720,p,3300,1620,750,30,17.982,23.976,59.341,1360,40,220,5,5,20,p,p +F,79,,1680,720,p,3300,1620,750,30,17.982,23.976,59.341,1360,40,220,5,5,20,p,p +F,79,,1680,720,p,3300,1620,750,30,18,24,59.4,1360,40,220,5,5,20,p,p +F,80,,1680,720,p,3168,1488,750,30,18.75,25,59.4,1228,40,220,5,5,20,p,p +F,81,,1680,720,p,2640,960,750,30,22.478,29.970,59.341,700,40,220,5,5,20,p,p +F,81,,1680,720,p,2640,960,750,30,22.5,30,59.4,700,40,220,5,5,20,p,p +F,82,,1680,720,p,2200,520,750,30,37.5,50,82.5,260,40,220,5,5,20,p,p +F,83,,1680,720,p,2200,520,750,30,44.955,59.940,98.901,260,40,220,5,5,20,p,p +F,83,,1680,720,p,2200,520,750,30,45,60,99,260,40,220,5,5,20,p,p +F,84,,1680,720,p,2000,320,825,105,82.5,100,165,60,40,220,5,5,95,p,p +F,85,,1680,720,p,2000,320,825,105,98.901,119.880,197.802,60,40,220,5,5,95,p,p +F,85,,1680,720,p,2000,320,825,105,99,120,198,60,40,220,5,5,95,p,p +F,86,,2560,1080,p,3750,1190,1100,20,26.374,23.976,98.901,998,44,148,4,5,11,p,p +F,86,,2560,1080,p,3750,1190,1100,20,26.4,24,99,998,44,148,4,5,11,p,p +F,87,,2560,1080,p,3200,640,1125,45,28.125,25,90,448,44,148,4,5,36,p,p +F,88,,2560,1080,p,3520,960,1125,45,33.716,29.970,118.681,768,44,148,4,5,36,p,p +F,88,,2560,1080,p,3520,960,1125,45,33.75,30,118.8,768,44,148,4,5,36,p,p +F,89,,2560,1080,p,3300,740,1125,45,56.25,50,185.625,548,44,148,4,5,36,p,p +F,90,,2560,1080,p,3000,440,1100,20,65.934,59.940,197.802,248,44,148,4,5,11,p,p +F,90,,2560,1080,p,3000,440,1100,20,66,60,198,248,44,148,4,5,11,p,p +F,91,,2560,1080,p,2970,410,1250,170,125,100,371.25,218,44,148,4,5,161,p,p +F,92,,2560,1080,p,3300,740,1250,170,149.850,119.880,494.505,548,44,148,4,5,161,p,p +F,92,,2560,1080,p,3300,740,1250,170,150,120,495,548,44,148,4,5,161,p,p +F,93,103,3840,2160,p,5500,1660,2250,90,53.946,23.976,296.703,1276,88,296,8,10,72,p,p +F,93,103,3840,2160,p,5500,1660,2250,90,54,24,297,1276,88,296,8,10,72,p,p +F,94,104,3840,2160,p,5280,1440,2250,90,56.25,25,297,1056,88,296,8,10,72,p,p +F,95,105,3840,2160,p,4400,560,2250,90,67.433,29.970,296.703,176,88,296,8,10,72,p,p +F,95,105,3840,2160,p,4400,560,2250,90,67.5,30,297,176,88,296,8,10,72,p,p +F,96,106,3840,2160,p,5280,1440,2250,90,112.5,50,594,1056,88,296,8,10,72,p,p +F,97,107,3840,2160,p,4400,560,2250,90,134.865,59.940,593.407,176,88,296,8,10,72,p,p +F,97,107,3840,2160,p,4400,560,2250,90,135,60,594,176,88,296,8,10,72,p,p +F,98,,4096,2160,p,5500,1404,2250,90,53.946,23.976,296.703,1020,88,296,8,10,72,p,p +F,98,,4096,2160,p,5500,1404,2250,90,54,24,297,1020,88,296,8,10,72,p,p +F,99,,4096,2160,p,5280,1184,2250,90,56.25,25,297,968,88,128,8,10,72,p,p +F,100,,4096,2160,p,4400,304,2250,90,67.433,29.970,296.703,88,88,128,8,10,72,p,p +F,100,,4096,2160,p,4400,304,2250,90,67.5,30,297,88,88,128,8,10,72,p,p +F,101,,4096,2160,p,5280,1184,2250,90,112.5,50,594,968,88,128,8,10,72,p,p +F,102,,4096,2160,p,4400,304,2250,90,134.865,59.940,593.407,88,88,128,8,10,72,p,p +F,102,,4096,2160,p,4400,304,2250,90,135,60,594,88,88,128,8,10,72,p,p +G,108,109,1280,720,p,2500,1220,750,30,35.964,47.952,89.910,960,40,220,5,5,20,p,p +G,108,109,1280,720,p,2500,1220,750,30,36,48,90,960,40,220,5,5,20,p,p +G,110,,1680,720,p,2750,1070,750,30,35.964,47.952,98.901,810,40,220,5,5,20,p,p +G,110,,1680,720,p,2750,1070,750,30,36,48,99,810,40,220,5,5,20,p,p +G,111,112,1920,1080,p,2750,830,1125,45,53.946,47.952,148.352,638,44,148,4,5,36,p,p +G,111,112,1920,1080,p,2750,830,1125,45,54,48,148.5,638,44,148,4,5,36,p,p +G,113,,2560,1080,p,3750,1190,1100,20,52.747,47.952,197.802,998,44,148,4,5,11,p,p +G,113,,2560,1080,p,3750,1190,1100,20,52.8,48,198,998,44,148,4,5,11,p,p +G,114,116,3840,2160,p,5500,1660,2250,90,107.892,47.952,593.407,1276,88,296,8,10,72,p,p +G,114,116,3840,2160,p,5500,1660,2250,90,108,48,594,1276,88,296,8,10,72,p,p +G,115,,4096,2160,p,5500,1404,2250,90,107.892,47.952,593.407,1020,88,296,8,10,72,p,p +G,115,,4096,2160,p,5500,1404,2250,90,108,48,594,1020,88,296,8,10,72,p,p +G,117,119,3840,2160,p,5280,1440,2250,90,225,100,1188,1056,88,296,8,10,72,p,p +G,118,120,3840,2160,p,4400,560,2250,90,269.730,119.880,1186.813,176,88,296,8,10,72,p,p +G,118,120,3840,2160,p,4400,560,2250,90,270,120,1188,176,88,296,8,10,72,p,p +G,121,,5120,2160,p,7500,2380,2200,40,52.747,23.976,395.604,1996,88,296,8,10,22,p,p +G,121,,5120,2160,p,7500,2380,2200,40,52.8,24,396,1996,88,296,8,10,22,p,p +G,122,,5120,2160,p,7200,2080,2200,40,55,25,396,1696,88,296,8,10,22,p,p +G,123,,5120,2160,p,6000,880,2200,40,65.934,29.970,395.604,664,88,128,8,10,22,p,p +G,123,,5120,2160,p,6000,880,2200,40,66,30,396,664,88,128,8,10,22,p,p +G,124,,5120,2160,p,6250,1130,2475,315,118.681,47.952,741.758,746,88,296,8,10,297,p,p +G,124,,5120,2160,p,6250,1130,2475,315,118.8,48,742.5,746,88,296,8,10,297,p,p +G,125,,5120,2160,p,6600,1480,2250,90,112.5,50,742.5,1096,88,296,8,10,72,p,p +G,126,,5120,2160,p,5500,380,2250,90,134.865,59.940,741.758,164,88,128,8,10,72,p,p +G,126,,5120,2160,p,5500,380,2250,90,135,60,742.5,164,88,128,8,10,72,p,p +G,127,,5120,2160,p,6600,1480,2250,90,225,100,1485,1096,88,296,8,10,72,p,p +G,193,,5120,2160,p,5500,380,2250,90,269.730,119.880,1483.516,164,88,128,8,10,72,p,p +G,193,,5120,2160,p,5500,380,2250,90,270,120,1485,164,88,128,8,10,72,p,p +G,194,202,7680,4320,p,11000,3320,4500,180,107.892,23.976,1186.813,2552,176,592,16,20,144,p,p +G,194,202,7680,4320,p,11000,3320,4500,180,108,24,1188,2552,176,592,16,20,144,p,p +G,195,203,7680,4320,p,10800,3120,4400,80,110,25,1188,2352,176,592,16,20,44,p,p +G,196,204,7680,4320,p,9000,1320,4400,80,131.868,29.970,1186.813,552,176,592,16,20,44,p,p +G,196,204,7680,4320,p,9000,1320,4400,80,132,30,1188,552,176,592,16,20,44,p,p +G,197,205,7680,4320,p,11000,3320,4500,180,215.784,47.952,2373.626,2552,176,592,16,20,144,p,p +G,197,205,7680,4320,p,11000,3320,4500,180,216,48,2376,2552,176,592,16,20,144,p,p +G,198,206,7680,4320,p,10800,3120,4400,80,220,50,2376,2352,176,592,16,20,44,p,p +G,199,207,7680,4320,p,9000,1320,4400,80,263.736,59.940,2373.626,552,176,592,16,20,44,p,p +G,199,207,7680,4320,p,9000,1320,4400,80,264,60,2376,552,176,592,16,20,44,p,p +G,200,208,7680,4320,p,10560,2880,4500,180,450,100,4752,2112,176,592,16,20,144,p,p +G,201,209,7680,4320,p,8800,1120,4500,180,449.550,119.880,4747.253,352,176,592,16,20,144,p,p +G,201,209,7680,4320,p,8800,1120,4500,180,450,120,4752,352,176,592,16,20,144,p,p +G,210,,10240,4320,p,12500,2260,4950,630,118.681,23.976,1483.516,1492,176,592,16,20,594,p,p +G,210,,10240,4320,p,12500,2260,4950,630,118.8,24,1485,1492,176,592,16,20,594,p,p +G,211,,10240,4320,p,13500,3260,4400,80,110,25,1485,2492,176,592,16,20,44,p,p +G,212,,10240,4320,p,11000,760,4500,180,134.865,29.970,1483.516,288,176,296,16,20,144,p,p +G,212,,10240,4320,p,11000,760,4500,180,135,30,1485,288,176,296,16,20,144,p,p +G,213,,10240,4320,p,12500,2260,4950,630,237.363,47.952,0.000,1492,176,592,16,20,594,p,p +G,213,,10240,4320,p,12500,2260,4950,630,237.6,48,,1492,176,592,16,20,594,p,p +G,214,,10240,4320,p,13500,3260,4400,80,220,50,2970,2492,176,592,16,20,44,p,p +G,215,,10240,4320,p,11000,760,4500,180,269.730,59.940,2967.033,288,176,296,16,20,144,p,p +G,215,,10240,4320,p,11000,760,4500,180,270,60,2970,288,176,296,16,20,144,p,p +G,216,,10240,4320,p,13200,2960,4500,180,450,100,5940,2192,176,592,16,20,144,p,p +G,217,,10240,4320,p,11000,760,4500,180,539.461,119.880,5934.066,288,176,296,16,20,144,p,p +G,217,,10240,4320,p,11000,760,4500,180,540,120,5940,288,176,296,16,20,144,p,p +G,218,,4096,2160,p,5280,1184,2250,90,225,100,1188,800,88,296,8,10,72,p,p +G,219,,4096,2160,p,4400,304,2250,90,269.730,119.880,1186.813,88,88,128,8,10,72,p,p +G,219,,4096,2160,p,4400,304,2250,90,270,120,1188,88,88,128,8,10,72,p,p \ No newline at end of file diff --git a/bandwidth/DebugControl.js b/bandwidth/DebugControl.js new file mode 100644 index 0000000..14450e7 --- /dev/null +++ b/bandwidth/DebugControl.js @@ -0,0 +1,40 @@ +DebugSettings = {}; + +function DEBUG(text) { + Fn = DEBUG.caller.name; + proceed_with_msg = false; + + if ('all' in DebugSettings) { + if (!DebugSettings['all']) { + return; + } // if All == false, always skip + else if (DebugSettings['all']) { + //printDebug(Fn, arguments); + proceed_with_msg = true; + } + } + else if (Fn in DebugSettings) { + if (DebugSettings[Fn] == false) { + //printDebug(Fn, arguments) + proceed_with_msg = false; + } + } + else { + proceed_with_msg = true; + } + + if (proceed_with_msg == true) { + str = 'console.log("[" + Fn + "()] ::"'; + for (var i = 0; i < arguments.length; i++) { + str += ', arguments[' + i + ']'; + } + str += ');'; + eval(str); + } + + return; +} + +function DebugConfig(settings) { + DebugSettings = settings; +} \ No newline at end of file diff --git a/bandwidth/bandwidth.js b/bandwidth/bandwidth.js new file mode 100644 index 0000000..83841ea --- /dev/null +++ b/bandwidth/bandwidth.js @@ -0,0 +1,1453 @@ +DebugConfig({ + //all: true, + generate_table: 0, + SI: 1, + SI_set_options: 1, + SI_set_precision: 0, + timingUIChange: 1, +}); + + +CTA861 = {}; + + +// The intepreted current state of all input fields is stored here +Global_InputVars = { + 'HRES': '', // Int + 'VRES': '', // Int + 'FREQ': '', // Float + 'COLOR_DEPTH': '', + 'PIXEL_FORMAT': '', + 'COMP': '', + 'SCAN': '', + 'MARGINS': '', + 'TIMING_STD': '', + 'V_FP': '', + 'V_BP': '', + 'V_SW': '', + 'H_FP': '', + 'H_BP': '', + 'H_SW': '', +} + + + +Encoding_Overhead = { + '8b/10b': 1.25, + '16b/18b': 1.125, +}; + +Interface = [ + { + name: "DisplayPort 1.3–1.4", + encoding: "8b/10b", + datarate: 25.92 * (10 ** 9), + },{ + name: "DisplayPort 1.2", + encoding: "8b/10b", + datarate: 17.28 * (10 ** 9), + },{ + name: "DisplayPort 1.0–1.1", + encoding: "8b/10b", + datarate: 8.64 * (10 ** 9), + },{ + name: "HDMI 2.1", + encoding: "16b/18b", + datarate: 48 * (10 ** 9) * (16/18), + },{ + name: "HDMI 2.0", + encoding: "8b/10b", + datarate: 14.4 * (10 ** 9), + },{ + name: "HDMI 1.3–1.4", + encoding: "8b/10b", + datarate: 8.16 * (10 ** 9), + },{ + name: "HDMI 1.0–1.2", + encoding: "8b/10b", + datarate: 3.96 * (10 ** 9), + },{ + name: "Dual-Link DVI", + encoding: "8b/10b", + datarate: 7.92 * (10 ** 9), + },{ + name: "Single-Link DVI", + encoding: "8b/10b", + datarate: 3.96 * (10 ** 9), + },{ + name: "Thunderbolt 3 (Gen 2)", + encoding: "unknown", + datarate: 40 * (10 ** 9), + },{ + name: "Thunderbolt 3 (Gen 1)", + encoding: "unknown", + datarate: 34.56 * (10 ** 9), + },{ + name: "Thunderbolt 2", + encoding: "unknown", + datarate: 17.28 * (10 ** 9), + },{ + name: "Thunderbolt", + encoding: "unknown", + datarate: 8.64 * (10 ** 9), + }, +]; + + + +function submitVar(id, val) { + DEBUG(id, val); + var len = 0; + var GlobalVars_Key = ''; + + if (typeof(val) === 'number') { val = val.toString(); } + + if (id == 'INPUT_HRES' || id == 'INPUT_VRES') { + if (id == 'INPUT_HRES') GlobalVars_Key = 'HRES'; + else if (id == 'INPUT_VRES') GlobalVars_Key = 'VRES'; + val = parseNum(val); + if (isNum(val) == true) { + val = Math.abs(parseInt(val)); + Global_InputVars[GlobalVars_Key] = val; + } + else { + DEBUG(id + ' input is not a number:', val); + Global_InputVars[GlobalVars_Key] = ''; + } + DEBUG("Global_InputVars['" + GlobalVars_Key + "'] was set to", Global_InputVars[GlobalVars_Key]); + } + + else if (id == 'INPUT_F') { + GlobalVars_Key = 'FREQ'; + val = parseNum(val); + if (isNum(val) == true) { + Global_InputVars[GlobalVars_Key] = Math.abs(val); + } + else { + DEBUG(id + ' input is not a number:', val); + Global_InputVars[GlobalVars_Key] = ''; + } + DEBUG("Global_InputVars['" + GlobalVars_Key + "'] was set to", Global_InputVars[GlobalVars_Key]); + } + + else if (id == 'COLOR_DEPTH_FORM') { + // Value is either the color depth number (in bits per pixel), or the string 'Custom' + + GlobalVars_Key = 'COLOR_DEPTH'; + colordepthUIChange(); + + if (val != 'Custom') { Global_InputVars[GlobalVars_Key] = val; } + else if (val == 'Custom') { // Custom color depth + // If Custom, grab the number from the custom color depth input field + val = parseInt(parseNum($('#CUSTOM_COLOR_DEPTH').val())); + if (isNum(val) == true) { + if ($('input[name=CD_UNIT_SLCT]:checked').val() == 'bpc') { + val = val * 3; // If user enters values in units of bpc, then multiply by 3 to convert to bit/px (bpp) + } + Global_InputVars[GlobalVars_Key] = Math.abs(val); + } + else { + DEBUG(id + ' input is not a number:', val); + Global_InputVars[GlobalVars_Key] = ''; + } + + } + DEBUG("Global_InputVars['" + GlobalVars_Key + "'] was set to", Global_InputVars[GlobalVars_Key]); + } + + else if (id == 'PIXEL_FORMAT_FORM') { + GlobalVars_Key = 'PIXEL_FORMAT'; + if (val == 'RGB' || val == 'YCBCR 4:4:4') { val = 1.0; } + else if (val == 'YCBCR 4:2:2') { val = 1.5; } + else if (val == 'YCBCR 4:2:0') { val = 2.0; } + Global_InputVars[GlobalVars_Key] = val; + DEBUG("Global_InputVars['" + GlobalVars_Key + "'] was set to", Global_InputVars[GlobalVars_Key]); + } + + else if (id == 'COMPRESSION_FORM') { + GlobalVars_Key = 'COMP'; + if (val == 'Uncompressed') { val = 1.0; } + else if (val == 'DSC 2.0x') { val = 2.0; } + else if (val == 'DSC 2.5x') { val = 2.5; } + else if (val == 'DSC 3.0x') { val = 3.0; } + Global_InputVars[GlobalVars_Key] = val; + DEBUG("Global_InputVars['" + GlobalVars_Key + "'] was set to", Global_InputVars[GlobalVars_Key]); + } + + else if (id == 'SCAN_FORM') { + GlobalVars_Key = 'SCAN'; + if (val == 'p') { val = 1; } + else if (val == 'i') { val = 2; } + Global_InputVars[GlobalVars_Key] = val; + DEBUG("Global_InputVars['" + GlobalVars_Key + "'] was set to", Global_InputVars[GlobalVars_Key]); + timingUIChange(); + DEBUG('Set interlaced timing row display to:', $('#Interlaced_Timing_Row').css('display')); + } + + else if (id == 'MARGINS_FORM') { + // val is either 'y' or 'n' + GlobalVars_Key = 'MARGINS'; + marginsUIChange(); + + if (val == 'n') { + Global_InputVars[GlobalVars_Key] = 0; + } + else if (val == 'y') { + val = parseFloat(parseNum($('#CUSTOM_MARGINS').val())); + if (isNum(val) == true) { + Global_InputVars[GlobalVars_Key] = Math.abs(val); + } + else { + DEBUG(id + ' input is not a number:', val); + Global_InputVars[GlobalVars_Key] = ''; + } + } + else { DEBUG('invalid input combination. id/val = ', id, val) } // If val is not 'y' or 'n', then something somewhere has gone terribly wrong + DEBUG("Global_InputVars['" + GlobalVars_Key + "'] was set to", Global_InputVars[GlobalVars_Key]); + } + + else if (id == 'TIMING_DROP') { + GlobalVars_Key = 'TIMING_STD'; + + Global_InputVars[GlobalVars_Key] = val; + DEBUG("Global_InputVars['" + GlobalVars_Key + "'] was set to", Global_InputVars[GlobalVars_Key]); + + timingUIChange(); + } + + else if (id == 'V_FP' || id == 'V_BP' || id == 'V_SW' || + id == 'H_FP' || id == 'H_BP' || id == 'H_SW' || + id == 'V_FP_ODD' || id == 'V_SW_ODD' || id == 'V_BP_ODD') { + GlobalVars_Key = id; + val = parseFloat(parseNum(val)); + if (isNum(val)) { + val = Math.abs(val); + Global_InputVars[id] = val; + $('#' + id).val(val); + $('#V_BLANK').html(Global_InputVars['V_FP'] + Global_InputVars['V_BP'] + Global_InputVars['V_SW']); + $('#H_BLANK').html(Global_InputVars['H_FP'] + Global_InputVars['H_BP'] + Global_InputVars['H_SW']); + $('#V_BLANK_ODD').html(Global_InputVars['V_FP_ODD'] + Global_InputVars['V_BP_ODD'] + Global_InputVars['V_SW_ODD']); + } + else { + DEBUG(id + ' input is not a number:', val); + Global_InputVars[id] = ''; + $('#' + id).val(''); + $('#V_BLANK').html(''); + $('#H_BLANK').html(''); + $('#V_BLANK_ODD').html(''); + } + DEBUG("Global_InputVars['" + GlobalVars_Key + "'] was set to", Global_InputVars[GlobalVars_Key]); + + // Update value displayed in the UI input field + + // Update total Vblank and Hblank numbers on the UI + } +} + + +function generate_table(type, input_datarate) { + if (type == "Interface Support") { + table = $("#interface_support_table"); + contents = ''; + contents += ('Interface Support'); + contents += (''); + contents += ('% Usage'); + contents += ('Interface'); + contents += ('Encoding'); + contents += ('Maximum'); + contents += ('Pixel ClockDatarateBandwidth'); + + var name; + var encoding; + var datarate; + var pixel_clock; + var bandwidth; + var overhead; + var saturation; + + for (var x = 0; x < Interface.length; x++) { + name = Interface[x]['name']; + encoding = Interface[x]['encoding']; + if (encoding == 'unknown') { + datarate = Interface[x]['datarate']; + bandwidth = '?'; + encoding = '?'; + } + else { + overhead = Encoding_Overhead[encoding]; + datarate = Interface[x]['datarate']; + bandwidth = datarate * overhead; + } + pixel_clock = datarate / 24; + saturation = input_datarate / datarate; + + if (input_datarate == -1) { + contents += (''); + } + else { + contents += ('' + saturation.toFixed(1) + '%'); + } + + contents += ('' + name + '' + encoding + '' + SI(pixel_clock, 'Hz', {p: ['M0', 'G3']}) + '' + SI(datarate, 'bit/s', 2) + '' + SI(bandwidth, 'bit/s', 2) + ''); + } + + table.html(contents); + + return; + } + + else if (type == "Maximum Refresh Frequency") { + return; + } + + + return; +} + + +function input_ok() { + // This checks to make sure all the necessary fields are filled in, and their contents are valid, before allowing the Calculation function to proceed. + var val; + + var checklist = ['HRES', 'VRES', 'FREQ']; + for (var i = 0; i < checklist.length; i++) { + val = Global_InputVars[checklist[i]]; + if (!(isPositive(val))) { + DEBUG('Calculation aborted. ' + checklist[i] + ' contains an empty or non-numeric string.', val); + return false; + } + } + + if ($('input[name=COLOR_DEPTH_SLCT]:checked').val() == 'Custom') { + val = Global_InputVars['COLOR_DEPTH']; + if (!(isPositive(val))) { + DEBUG('Calculation aborted. COLOR_DEPTH_SLCT is set to Custom, but CUSTOM_COLOR_DEPTH contains an empty or non-numeric string, or 0.', val); + return false; + } + } + + if (Global_InputVars['TIMING_STD'] == 'Custom') { + checklist = ['V_FP', 'V_BP', 'V_SW', 'H_FP', 'H_BP', 'H_SW']; + var abort = false; + for (var i = 0; i < checklist.length; i++) { + val = Global_InputVars[checklist[i]]; + if (!(isPositiveZ(val))) { + abort = true; + } + } + if (abort == true) { + DEBUG('Calculation aborted. TIMING_DROP is set to Custom, but one or more timing parameter fields contains an empty or non-numeric string.', Global_InputVars); + return false; + } + } + + if ($('input[name=MARGINS_SLCT]:checked').val() == 'y') { + val = Global_InputVars['MARGINS']; + if (!(isPositiveZ(val))) { + DEBUG('Calculation aborted. MARGINS_SLCT is set to Yes, but CUSTOM_MARGINS contains an empty or non-numeric string.', val); + return false; + } + } + + return true; +} + + +function calcMain() { + if (!input_ok()) { clearResults(); return; } + + var hres = Global_InputVars['HRES']; + var vres = Global_InputVars['VRES']; + var freq = Global_InputVars['FREQ']; + + var color_depth = Global_InputVars['COLOR_DEPTH']; + var px_format = Global_InputVars['PIXEL_FORMAT']; + var comp = Global_InputVars['COMP']; + var scan = Global_InputVars['SCAN']; + var timing_standard = Global_InputVars['TIMING_STD']; + + // Get timing parameters + Timing = getTiming(timing_standard); + DEBUG('Timing:', Timing); + + var results = { + hres: hres, + vres: vres, + freq: freq, + px_bits: color_depth, + px_format: px_format, + comp: comp, + scan: scan, + + Timing: Timing, + + // Calculate results + // Resolution with blanking intervals + h_eff: Timing['H_EFF'], + v_eff: Timing['V_EFF'], + + // Aspect ratio + ratio_num: hres / vres, + ratio_str: (hres / GCD(hres, vres)) + ':' + (vres / GCD(hres, vres)), + + // Total pixel count of image + px_per_frame: hres * vres, + px_per_frame_eff: (Timing['H_EFF']) * (Timing['V_EFF']), + + // Size (bits) of one frame + bits_per_frame: hres * vres * color_depth, + bits_per_frame_eff: (Timing['H_EFF']) * (Timing['V_EFF']) * color_depth, + + // Pixel clock + px_per_sec: hres * vres * Timing['F_ACTUAL'], + px_per_sec_eff: (Timing['H_EFF']) * (Timing['V_EFF']) * Timing['F_ACTUAL'], + + // Raw bit rate + bits_per_sec_eff: (Timing['H_EFF']) * (Timing['V_EFF']) * color_depth * Timing['F_ACTUAL'], + }; + + DEBUG('Results:', SI(results['bits_per_sec_eff'], 'bit/s', 2), results); + updateDisplay(results); +} + + +function getTiming(timing_standard) { + // Just a traffic control function + // Actual parameters are calculated in dedicated functions for each standard + + if (!(isPositiveZ(Global_InputVars['HRES']) && isPositiveZ(Global_InputVars['VRES']) && isPositiveZ(Global_InputVars['FREQ']))) { + DEBUG('Timing calculation aborted, HRES, VRES, or FREQ contains invalid input.', Global_InputVars['HRES'], Global_InputVars['VRES'], Global_InputVars['FREQ']); + return { + 'V_FP': '', + 'V_BP': '', + 'V_SW': '', + 'H_FP': '', + 'H_BP': '', + 'H_SW': '', + + 'V_BL': '', + 'H_BL': '', + + 'F_ACTUAL': Global_InputVars['FREQ'], + }; + } + + if (timing_standard != 'Custom') { + if (timing_standard == 'CVT-R2') { + Timing = CVT_R(2); + } + else if (timing_standard == 'CVT-RB') { + Timing = CVT_R(1); + } + else if (timing_standard == 'CVT') { + Timing = CVT(); + } + else if (timing_standard == 'GTF') { + Timing = GTF(); + } + else if (timing_standard == 'DMT') { + Timing = DMT(); + } + else if (timing_standard == 'CTA-861') { + Timing = CTA(); + } + else if (timing_standard == 'None') { + Timing = { + 'V_FP': 0, + 'V_BP': 0, + 'V_SW': 0, + 'H_FP': 0, + 'H_BP': 0, + 'H_SW': 0, + + 'V_FP_ODD': 0, + 'V_BP_ODD': 0, + 'V_SW_ODD': 0, + + 'V_BL': 0, + 'H_BL': 0, + + 'F_ACTUAL': Global_InputVars['FREQ'], + } + } + // Update UI timing parameter fields with the newly generated timings + submitVar('V_FP', Timing['V_FP']); + submitVar('V_BP', Timing['V_BP']); + submitVar('V_SW', Timing['V_SW']); + submitVar('H_FP', Timing['H_FP']); + submitVar('H_BP', Timing['H_BP']); + submitVar('H_SW', Timing['H_SW']); + submitVar('V_FP_ODD', Timing['V_FP_ODD']); + submitVar('V_SW_ODD', Timing['V_SW_ODD']); + submitVar('V_BP_ODD', Timing['V_BP_ODD']); + } + + else if (timing_standard == 'Custom') { + // Read the timing from the UI + submitVar('V_FP', $('#V_FP').val()); + submitVar('V_BP', $('#V_BP').val()); + submitVar('V_SW', $('#V_SW').val()); + submitVar('H_FP', $('#H_FP').val()); + submitVar('H_BP', $('#H_BP').val()); + submitVar('H_SW', $('#H_SW').val()); + submitVar('V_FP_ODD', Global_InputVars['V_FP'] + 0.5); + submitVar('V_SW_ODD', Global_InputVars['V_SW']); + submitVar('V_BP_ODD', Global_InputVars['V_BP'] + 0.5); + + Timing = { + 'V_FP': Global_InputVars['V_FP'], + 'V_BP': Global_InputVars['V_BP'], + 'V_SW': Global_InputVars['V_SW'], + 'H_FP': Global_InputVars['H_FP'], + 'H_BP': Global_InputVars['H_BP'], + 'H_SW': Global_InputVars['H_SW'], + + 'V_FP_ODD': Global_InputVars['V_FP_ODD'], + 'V_BP_ODD': Global_InputVars['V_BP_ODD'], + 'V_SW_ODD': Global_InputVars['V_SW_ODD'], + + 'V_BL': Global_InputVars['V_FP'] + Global_InputVars['V_BP'] + Global_InputVars['V_SW'], + 'H_BL': Global_InputVars['H_FP'] + Global_InputVars['H_BP'] + Global_InputVars['H_SW'], + + 'F_ACTUAL': Global_InputVars['FREQ'], + } + } + + $('#V_BLANK').html(Timing['V_BL']); + $('#H_BLANK').html(Timing['H_BL']); + + return Timing; +} + + +function CVT_R(R) { // Variable R is an integer representing the reduced blanking revision to use, version 1 or version 2. + var H = Global_InputVars['HRES']; // Horizontal active pixels + var V = Global_InputVars['VRES']; // Vertical active pixels + var F = Global_InputVars['FREQ']; // Nominal vertical refresh frequency + var S = Global_InputVars['SCAN']; // 1 for progressive scan, 2 for interlaced + var M = Global_InputVars['MARGINS']; // Margins (%) + + // Declaring variables for all results + var V_FP; // Vertical front porch + var V_SW; // Vertical sync width + var V_BP; // Vertical back porch + + var H_FP; // Horizontal front porch + var H_SW; // Horizontal sync width + var H_BP; // Horizontal back porch + + var V_BLANK; // Total vertical blanking (V_FP + V_SW + V_BP) + var H_BLANK; // Total horizontal blanking (H_FP + H_SW + H_BP) + var V_EFF; // V + V_Blank + V_Margins + var H_EFF; // H + H_Blank + H_Margins + + var F_ACTUAL; // Actual vertical refresh frequency (after pixel clock rounding) + var F_HOR; // Horizontal refresh frequency + + // Common constants + var V_PER_MIN = 0.00046; /* Minimum vertical blanking period for reduced blank timing (in seconds), defined by VESA CVT 1.2 standard */ + var V_LINES = Math.floor(V / S) // If progressive scan, S = 1 and V_LINES = V. If interlaced, V_LINES = floor(V / 2). + + if (R == 1) { + // CVT-RB constants + H_BLANK = 160; + H_FP = 48; + H_BP = 80; + H_SW = 32; + V_FP = 3; + var V_BP_MIN = 6; + + // All H timings are defined, as well as V_FP. Only V_SW and V_BP remain (and V_BLANK, the sum of all 3 V parameters) + + // Determine vertical sync width (V_SW) from table of magic numbers defined in VESA CVT standard + var V_SYNC_TABLE = [ + [4/3, 4], + [16/9, 5], + [8/5, 6], + [5/3, 7], + [5/4, 7], + ] + V_SW = 10; // default value defined in standard + for (var i = 0; i < V_SYNC_TABLE.length; i++) { + if ((H / V) - V_SYNC_TABLE[i][0] < 0.05) { // Check if aspect ratio of image (H/V) matches an aspect ratio defined in table, within 0.05 + V_SW = V_SYNC_TABLE[i][1]; + } + } + + // V_BP is determined in reverse, by calculating V_BLANK first (the sum of V_FP, V_SW, and V_BP) and subtracting out V_FP and V_SW. + + var CellGran = 8; // Cell granularity constant defined by CVT standard + var H_RND = Math.floor(H / CellGran) * CellGran; // Round down horizontal resolution to be a multiple 8 + var V_MARGIN = Math.floor(M / 100) * V_LINES; // If margins percent (M) is 0, this result is 0 + var H_MARGIN = Math.floor(H_RND * M / 100 / CellGran) * CellGran; // If margins percent (M) is 0, this result is 0 + + var H_PER_EST = ((1 / F) - V_PER_MIN) / (V_LINES + (2 * V_MARGIN)); // Horizontal blanking period estimate + V_BLANK = Math.floor((V_PER_MIN / H_PER_EST) + 1); + + var V_BLANK_MIN = V_FP + V_SW + V_BP_MIN; + if (V_BLANK < V_BLANK_MIN) { V_BLANK = V_BLANK_MIN; } // Enforce minimum value for V_blank + + V_BP = V_BLANK - (V_FP + V_SW); + + V_EFF = V_LINES + V_BLANK + V_MARGIN + ((S - 1)/2); // (S-1)/2 = 0 for progressive, 0.5 for interlaced + H_EFF = H_RND + H_BLANK + H_MARGIN; + + // Calculate pixel clock, to enforce pixel clock rounding to the nearest 250 kHz, as required by the CVT standard + var CLK = F * (V_EFF) * (H_EFF); // Pixel clock (Hz) + CLK = Math.floor(CLK / 250000) * 250000; // Pixel clock (Hz) rounded down to the next multiple of 0.25 MHz (250 kHz, 250000 Hz) + + F_HOR = CLK / (H_EFF); // Horizontal refresh frequency (Hz) + F_ACTUAL = F_HOR / (V_EFF); // Pixel clock rounding is enforced via lowering the vertical refresh frequency to adjust pixel clock + } + else if (R == 2) { + // CVT-R2 constants defined by CVT standard + H_BLANK = 80; + H_FP = 8; + H_BP = 40; + H_SW = 32; + V_BP = 6; + V_SW = 8; + var V_FP_MIN = 1; + + // All parameters are defined as constant except V_FP + // V_FP is determined in reverse, by calculating V_BLANK first (the sum of V_FP, V_SW, and V_BP) and subtracting V_SW and V_BP out. + + var V_MARGIN = Math.floor(M / 100) * V_LINES; // If margins percent (M) is 0, this result is 0 + var H_MARGIN = Math.floor(H * M / 100); // If margins percent (M) is 0, this result is 0 + + var H_PER_EST = ((1 / F) - V_PER_MIN) / (V_LINES + (2 * V_MARGIN)); // Horizontal blanking period estimate + V_BLANK = Math.floor((V_PER_MIN / H_PER_EST) + 1); + + var V_BLANK_MIN = V_FP_MIN + V_SW + V_BP; + if (V_BLANK < V_BLANK_MIN) { V_BLANK = V_BLANK_MIN; } // Enforce minimum value for V_blank + + V_FP = V_BLANK - (V_BP + V_SW); + + V_EFF = V_LINES + V_BLANK + V_MARGIN + ((S - 1)/2); // (S-1)/2 = 0 for progressive, 0.5 for interlaced + H_EFF = H + H_BLANK + H_MARGIN; + + // Calculate pixel clock, to enforce pixel clock rounding to the nearest 1 kHz, as required by the CVT standard + var CLK = F * (V_EFF) * (H_EFF); // Pixel clock (Hz) + CLK = Math.floor(CLK / 1000) * 1000; // Pixel clock (Hz) rounded down to the next multiple of 0.001 MHz (1 kHz, 1000 Hz) + + F_HOR = CLK / (H_EFF); // Horizontal refresh frequency (Hz) + F_ACTUAL = F_HOR / (V_EFF); + } + //DEBUG('CLK', CLK); + //DEBUG('F_HOR', F_HOR); + //DEBUG('F_ACTUAL', F_ACTUAL); + + //DEBUG(V_BLANK); + return { + V_FP: V_FP, // For interlaced, these vertical timing are used for the even fields + V_BP: V_BP, + V_SW: V_SW, + H_FP: H_FP, + H_BP: H_BP, + H_SW: H_SW, + + V_FP_ODD: V_FP + 0.5, // For interlaced, V_FP and V_BP are 0.5 higher for odd fields (V_SW is same) + V_SW_ODD: V_SW, + V_BP_ODD: V_BP + 0.5, + + V_BL: V_BLANK, + H_BL: H_BLANK, + + V_EFF: V_EFF, + H_EFF: H_EFF, + + F_ACTUAL: F_ACTUAL, + }; +} + + +function CTA() { + +} + + +function updateDisplay() { + return; +} + + +function clearResults() { + return; +} + +function timingUIChange() { + // Controls the enabled/disabled state of the custom timing format input fields + var timing_params = [ + $('#V_FP'), + $('#V_BP'), + $('#V_SW'), + $('#H_FP'), + $('#H_BP'), + $('#H_SW'), + ]; + value = $('#TIMING_DROP').val(); + if (value == 'Custom') { + for (var i = 0; i < timing_params.length; i++) { + param = timing_params[i]; + param.prop('disabled', false); + //if (field.prop('oldvalue') != '') { + // field.val(field.prop('oldvalue')); + //} + } + } + + else { + for (var i = 0; i < timing_params.length; i++) { + param = timing_params[i]; + param.prop('disabled', true); + //field.prop('oldvalue', field.val()); + //field.val(''); + } + } + value = $('input[name=SCAN_SLCT]:checked').val(); + if (value == 'i') { + $('#V_BLANK_ODD_LABEL').css('display', 'table-cell'); + $('#V_FP_ODD_CONTAINER').css('display', 'table-cell'); + $('#V_BP_ODD_CONTAINER').css('display', 'table-cell'); + $('#V_SW_ODD_CONTAINER').css('display', 'table-cell'); + $('#V_BLANK_ODD_CONTAINER').css('display', 'table-cell'); + $('#V_BLANK_EVEN_LABEL').html('(Even) Vblank'); + } + else if (value == 'p') { + $('#V_BLANK_ODD_LABEL').css('display', 'none'); + $('#V_FP_ODD_CONTAINER').css('display', 'none'); + $('#V_BP_ODD_CONTAINER').css('display', 'none'); + $('#V_SW_ODD_CONTAINER').css('display', 'none'); + $('#V_BLANK_ODD_CONTAINER').css('display', 'none'); + $('#V_BLANK_EVEN_LABEL').html('Vblank'); + } + else { + DEBUG('Something somewhere has gone terribly wrong. Attemped to grab SCAN_SLCT value, and it was neither "p" nor "i"!'); + } +} + + +function colordepthUIChange() { + // Controls the enabled/disabled state of the custom timing format input fields + var field = $('#CUSTOM_COLOR_DEPTH'); + value = $('input[name=COLOR_DEPTH_SLCT]:checked').val(); + if (value == 'Custom') { + field.prop('disabled', false); + $('input[name=CD_UNIT_SLCT]').prop('disabled', false); + } + + else { + field.prop('disabled', true); + $('input[name=CD_UNIT_SLCT]').prop('disabled', true); + } +} + + +function marginsUIChange() { + // Controls the enabled/disabled state of the custom timing format input fields + var field = $('#CUSTOM_MARGINS'); + value = $('input[name=MARGINS_SLCT]:checked').val(); + if (value == 'y') { + field.prop('disabled', false); + if (field.val() == '') { + field.val('1.8'); + } + } + + else { + field.prop('disabled', true); + } +} + + +function SI(value, unit, options_input) { + DEBUG('Input:', value, unit, options_input); + if (isNaN(parseInt(value))) { return value; } + if (typeof(options_input) == 'number' || typeof(options_input) == 'string') { options_input = {p: options_input.toString()}; } + + /* Valid options: + SI_options = { + p: 2 Specifies default precision (number of decimal places). Use '-1' or 'a' or 'adaptive' for adaptive mode (autodetect decimal places) + Also accepts individual decimal place settings for each prefix, in array format; for example: + {p: [1, G2, M0]} sets a default of 1 for all prefixes, then specifies 2 decimal places for Giga and 0 for Mega. + p_max: 4 Maximum number of decimal places (for adaptive precision mode) + p_min: 2 Minimum number of decimal places (for adaptive precision mode) + + separator: ',' Character to use as thousands separator (default comma) + decimal: '.' Character to use as decimal point (default period) + unit_sep: ' ' Character to place between the value and unit symbol (default non-breaking space) + cap_kilo: 'true' Use capital K for kilo instead of small k. (default false) + no_mu: 'true' Use "u" instead of "µ" on output, if necessary for compatibility reasons (default false) + + exclude: ['c', 'd'] SI prefixes to exclude, for situational use (i.e. for displaying time, one may not wish for "centiseconds"). + Symbols can also be used as shortcuts, as in the following examples: + '>=G' excludes Giga and larger + '>G' excludes larger than Giga, but not Giga itself + '= -24 && magnitude <= 24) { + // Change any inbetween magnitudes to one that has a prefix assigned to it + /* + if (!(magnitude in prefixDef)) { + magnitude = 3 * Math.floor(magnitude / 3); + }*/ + while (!(magnitude in prefixDef)) { + magnitude--; + } + // Get the precision specified for that magnitude + precision = prefixDef[magnitude]['p']; + + // Divide the number by the appropriate power of 10, and return the new number and associated SI prefix + out_value = Commas(Number(value / Math.pow(10, magnitude)).toFixed(precision)); + out_prefix = prefixDef[magnitude]['sym']; + } + else { + out_value = Commas(value); + out_prefix = ''; + } + + return (out_value + ' ' + out_prefix + unit); +} + + +function SI_set_options(SI_options, options_input, prefixDef, pre2num) { + var opt = ['p', 'p_max', 'p_min', 'separator', 'decimal', 'unit_sep', 'cap_k', 'no_mu']; + // Loop through all options, if specified by options_input, replace the default + for (var i = 0; i < opt.length; i++) { + if (opt[i] in options_input) { + SI_options[opt[i]] = options_input[opt[i]]; + } + } + var explicit_incl = []; + var arg; + var offset; + + if (!('exclude' in options_input)) { + DEBUG('options_input contained no exclusions'); + } + if (typeof(options_input['exclude']) == 'string') { + DEBUG('(SI_options == string) returns true'); + } + + else if (typeof(options_input['exclude']) == 'object') { + DEBUG('(SI_options == object) returns true'); + + // First pass, loops through [exclude] list provided in the input options, to check for any prefixes declared as Explicitly Included, with '!' + for (var i = 0; i < options_input['exclude'].length; i++) { + arg = options_input['exclude'][i]; + if (isNaN(parseInt(arg[0]))) { + // If arg[0] is !, add arg[1] to the list of explicitly included prefixes + if (arg[0] == '!' && pre2num[arg[1]] in prefixDef) { + explicit_incl.push(arg[1]); + } + } + } + DEBUG('Explicit inclusions:', explicit_incl); + // Second pass, loops through [exclude] list to look for '*', which excludes all prefixes except the ones on the explicit_incl list. + for (var i = 0; i < options_input['exclude'].length; i++) { + arg = options_input['exclude'][i]; + if (isNaN(parseInt(arg[0]))) { + if (arg[0] == '*') { + // If arg[0] is *, then exclude all prefixes except those explicitly included + for (var j = -24; j <= 24; j++ ) { + if (j in prefixDef) { + if (explicit_incl.indexOf(prefixDef[j]['sym']) == -1 && !(prefixDef[j]['sym'] in SI_options['exclude'])) { + SI_options['exclude'].push(prefixDef[j]['sym']); + DEBUG('Prefix "' + prefixDef[j]['sym'] + '" excluded'); + DEBUG(prefixDef[j]['sym'] + ' not in explicit_incl: ' + !(prefixDef[j]['sym'] in explicit_incl)); + } + } + } + break; + } + } + } + // Third pass, loops through [exclude] list again, to expand all shortcuts like ">=G" into "[G, T, P, E ... ]" + for (var i = 0; i < options_input['exclude'].length; i++) { + arg = options_input['exclude'][i]; + if (isNaN(parseInt(arg[0]))) { + if (arg[0] == '>') { + if (arg[1] == '=') { offset = 0; DEBUG('Offset set to 0') } // If command is ">=", offset 0 to include starting position. + else { offset = 1; } // Otherwise, command is ">", offset 1 to not include the specified prefix + for (var j = pre2num[arg[1 + (1-offset)]] + offset; j <= 24; j++) { + DEBUG('J:', j); + if (j in prefixDef) { + // Check to make sure it hasn't been explicitly included (present in explicit_incl list) or duplicate (present in SI[exclude]) + if (explicit_incl.indexOf(prefixDef[j]['sym']) == -1 && !(prefixDef[j]['sym'] in SI_options['exclude'])) { + // Exclude prefixes at/above the prefix specified by arg[1] + SI_options['exclude'].push(prefixDef[j]['sym']); + } + } + } + } + else if (arg[0] == '<') { + if (arg[1] == '=') { offset = 0; } // If command is "<=", offset 0 + else { offset = 1; } // Otherwise, command is "<", offset 1 to include the specified prefix + for (var j = pre2num[arg[1 + (1-offset)]] - offset; j >= -24; j--) { + if (j in prefixDef) { + // Check to make sure it hasn't been explicitly included (present in explicit_incl list) or duplicate (present in SI[exclude]) + if (explicit_incl.indexOf(prefixDef[j]['sym']) == -1 && !(prefixDef[j]['sym'] in SI_options['exclude'])) { + // Exclude prefixes at/below the prefix specified by arg[1] + SI_options['exclude'].push(prefixDef[j]['sym']); + } + } + } + } + else { + j = pre2num[arg[0]]; + if (j in prefixDef) { + if (explicit_incl.indexOf(prefixDef[j]['sym']) == -1 && !(prefixDef[j]['sym'] in SI_options['exclude'])) { + SI_options['exclude'].push(prefixDef[j]['sym']); + } + } + } + } + } + + } + return SI_options; +} + + +function SI_set_precision(SI_options, prefixDef, pre2num) { + for (var i = 0; i < SI_options['exclude'].length; i++) { + //prefixDef[pre2num[SI_options['exclude'][i]]]['excl'] = true; + // Delete prefixes that have been excluded + delete prefixDef[pre2num[SI_options['exclude'][i]]]; + } + var precision = SI_options['p']; + + // If the precision argument is a string, then there is only 1 precision specified + if (typeof(precision) == 'string') { + + // If it's a 1-character string, then it's a pure number, which means it is set as the default for all prefixes + if (precision.length == 1) { + for (var i = -24; i <= 24; i++) { + if (i.toString() in prefixDef) { + prefixDef[i.toString()]['p'] = precision; + } + } + } + // If it's a 2-character string, it could be a 2 digit number or a prefix-specific code + else if (precision.length == 2) { + if (isNaN(parseInt(precision[0]))) { + prefixDef[pre2num[precision[0]]]['p'] = parseInt(precision[1]); + } + } + } + else if (typeof(precision) == 'object') { + for (var j = 0; j < precision.length; j++) { + if (precision[j].length == 1) { + for (var i = -24; i <= 24; i++) { + if (i.toString() in prefixDef) { + prefixDef[i.toString()]['p'] = parseInt(precision[j]); + } + } + } + // If it's a 2-character string, it could be a 2 digit number or a prefix-specific code + else if (precision[j].length == 2) { + if (isNaN(parseInt(precision[j][0])) && (pre2num[precision[j][0]] in prefixDef)) { + prefixDef[pre2num[precision[j][0]]]['p'] = parseInt(precision[j][1]); + } + } + } + } + return prefixDef +} + + +function Load_CTA_861() { + // Loads the timing definitions for the CTA-861 standard from a csv file + fetch('CTA861.txt') + .then( + function(response) { + if (response.status !== 200 && response.status !== 0) { + console.log('Response code:', response.status); + return; + } + CTA861 = data; + } + ).then( + function() { + DEBUG(CTA861); + } + ) +} + + +//Small functions +{ +function Commas(num) { + var parts = num.toString().split("."); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); + return parts.join("."); +} + + +function getPrecision(x, watchdog) { + // https://stackoverflow.com/questions/27082377/get-number-of-decimal-places-with-javascript + x = Math.abs(x); + watchdog = watchdog || 32; + var i = 0; + while (x % 1 > 0 && i < watchdog) { + i++; + x = x * 10; + } + return i; +} + + +function GCD(a, b) { + a = Math.abs(a); + b = Math.abs(b); + if (b > a) { var temp = a; a = b; b = temp; } + while (true) { + if (b == 0) return a; + a %= b; + if (a == 0) return b; + b %= a; + } +} + + +function SameRatio(H, V, A) { + /* Checks if the ratio H/V is equal to the given ratio A (within a defined margin of error E) */ + /* Negative signs on H, V, and A are ignored */ + + var E = 0.001; /* E is a percent error written as a decimal (i.e. E = 0.001 would give an acceptable error margin of 0.1%) */ + + if (Math.abs((Math.abs(H / V) / Math.abs(A)) - 1) <= E) { return true; } + else { return false; } +} + + + /* +function parseNum(val) { + // Converts string to floating point if it has a decimal point, or integer if there is no decimal point. Also strips commas and spaces, and optionally applies absolute value. + + if (typeof val === "string") { + // val = val.replace(/[^0-9\. ]/g, ''); // Apply absolute value and ignore non-numeric characters + // val = val.replace(/[^0-9\. -]/g, ''); // Allow negative numbers and ignore non-numeric characters + + //val = val.replace(/-/g, ''); // Remove minus signs + + // Return NaN if... + if (val == '' // input is blank + || val.indexOf('.') != val.lastIndexOf('.') // input contains multiple decimal places + || val.indexOf('-') != val.lastIndexOf('-') // input contains multiple minus signs + || (val.indexOf('-') != -1 && val.indexOf('-') != 0) // input contains a minus sign in a position other than the first character + || val.match(/[^0-9.-]/g)) // input contains any character other than a number, decimal, minus, or space + { + return NaN; + } + + else { + // Once we have checked that the input contains no characters other than numerals, periods, and minus signs + // And that there are at most one period and one minus sign, and both are in valid positions, we can evaluate the number as either float or string. + // If for some reason either of them fail, it will still return NaN anyway + if (val.indexOf('.') == -1) + return parseInt(val); + else { + return parseFloat(val); + } + } + } + else if (Number.isNaN(val)) { + return NaN; // Check if val is literally 'NaN' + } + else if (typeof val === "number") { + return val; // NaN is recognized as a number, so this line must be after NaN is handled + } + else { + return NaN; + } +}*/ + + +function parseNum(val) { + // This function is designed to interpret strings with formatted numbers (which may contain currencies, digit grouping commas, units, etc.) + // It will return NaN if it cannot be interpreted as a valid number (i.e. no numeric characters, multiple periods or minus signs, etc.) + + if (typeof(val) === 'number') { + // If the input argument is already a number, then nothing needs to be done, simply return the input value + if (Number.isNaN(val) == true) { + // However, we do need to check that it isn't NaN, because that is identified as a number. + return NaN; + } + else { + return val; + } + } + + else if (typeof val === 'string') { + // First, remove all non-numeric characters on the outsides of the string + for (var i = 0; i < val.length; i++) { + if (!(i < val.length)) { break; } + //DEBUG('i:', i, 'val:', val, 'val[i]:', val[i]); + // Loop through each character starting from the front + if ((/[^0-9.-]/g).test(val[i]) == true) { + // If character is not a number, period, or minus sign, remove it + if (i == 0 && val.length > 1) { val = val.slice(1); } + else if (i == val.length - 1) { return NaN; } // If this is the last character in the string + else if (i > 0) { val = (val.slice(0, i)) + (val.slice(i+1));} + i = i - 1; // Since a character has been removed, the next character is now at the same index + continue; + } + else if ((/[-]/g).test(val[i]) == true) { + // If character is a negative sign, ignore it. This is because there may still be non-number characters between the negative sign and the first digit, such as "-$1.00". The negative sign should stay but the dollar needs to be removed. + continue; + } + else if ((/[.]/g).test(val[i]) == true) { + // If character is a period, then following character MUST be a digit, unless it's the end of the number. + if (i + 1 < val.length) { + if ((/[0-9]/g).test(val[i+1]) == true) { + // If the character after the period is a digit, then the first digit has been reached + break; + } + else { + // If the string contained a period followed by a non-numeric character, it is not a number + return NaN; + } + } + else { + // If i+1 was not < val.length, then the period is the last character in the string, which implicitly means the string contains no numeric characters. + return NaN; + } + } + else if ((/[0-9]/g).test(val[i]) == true) { + // If character was a numeric character, we have successfully stripped any leading characters and reached the start of the number. + break; + } + } + + // Now do a similar starting from the backside, to strip any trailing characters (such as units of measurement) + for (var i = (val.length - 1); i >= 0; i--) { + if ((/[^0-9]/g).test(val[i]) == true) { + // No need to modify iterator for this direction + // Since we are dealing with the end of the number, minus signs are no longer part of the number. Periods at the end are valid, but superfluous + // Since we are only checking the characters after the digits have ended, minus signs are not invalid, as they may be part of a unit of measurement. Therefore, they are simply removed, rather than returning NaN. + if (val.length == 1) { return NaN; } + else { val = val.slice(0, i); } + continue; + } + else if ((/[0-9]/g).test(val[i]) == true) { + // If character is a numeric character, we have reached the back end of the number and successfully stripped any trailing characters. + break; + } + } + + // We now strip any commas and whitespace throughout the string + val = val.replace(/[, ]/g, ''); + // If, after removing leading and trailing units, whitespace, and commas, there are any non-numeric characters (i.e. in the middle of the string after the numbers start), it is not a valid number + if ((/[^0-9.-]/g).test(val) == true) { + return NaN; + } + // Now that the string only contains numeric characters, minus signs, and periods, we must check if there are any invalid usages of the periods and signs, such as multiple periods/signs, or a minus sign anywhere other than the first character. + + if (val == '' // string is empty + || val.indexOf('.') != val.lastIndexOf('.') // string contains multiple decimal point + || val.indexOf('-') != val.lastIndexOf('-') // string contains multiple minus signs + || (val.indexOf('-') != -1 && val.indexOf('-') != 0)) // input contains a minus sign and it isn't the first character + { + return NaN; + } + + // Finished; string now only contains numbers, up to one period, and up to one minus sign as the first character of the string. + + // If for some reason the parseFloat fails, the function will return NaN, so the output is still consistent with any other failed condition. + return parseFloat(val); + } +} + + +function isNum(num) { + // Returns false if input is not a number, including a blank string or NaN + // Can accept arrays, and returns true only if all elements would return true individually + if (Array.isArray(num) == true) { + for (a = 0; a < num.length; a++) { + if (Number.isNaN(parseFloat(num[a])) == true) { + return false; + } + return true; + } + } + else { + return !Number.isNaN(parseFloat(num)); + } +} + + +function isPositive(num) { + + if (Array.isArray(num) == true) { + for (a = 0; a < num.length; a++) { + if (Number.isNaN(parseNum(num[a])) == true) + return false; + else if (num[a] <= 0) + return false; + } + return true; + } + else { + if (Number.isNaN(parseNum(num)) == true) + return false; + else if (num > 0) + return true; + else { + return false; + } + } +} + + +function isPositiveZ(num) { + // Returns true if input is positive or zero. Can recognize formatted numbers (i.e. "1,234,567 Gbit/s" or "-$1,234 USD") as allowed by parseNum + // Combine with isNum() to allow only true (non-formatted) numbers and strings (i.e. if (isNum(x) && isPositiveZ(x)) + + /* + + True: + + isPositiveZ(1) + isPositiveZ('1') + isPositiveZ(1.1) + isPositiveZ('1.1') + isPositiveZ('1.') Number string with stray decimal + isPositiveZ(0) or ('0') + isPositiveZ(Infinity) + isPositiveZ([1, '2', 3.3]) true iff all elements are non-negative + + False: + + isPositiveZ(-1) + isPositiveZ('-1') + isPositiveZ(-1.1) + isPositiveZ('-1.1') + isPositiveZ('1 ') Number with whitespace + isPositiveZ('') Empty String + isPositiveZ('abc') + isPositiveZ(NaN) + isPositiveZ([-1, '2', 3.3]) If any element is negative + + */ + + if (Array.isArray(num) == true) { + for (a = 0; a < num.length; a++) { + if (Number.isNaN(parseNum(num[a])) == true) + return false; + else if (num[a] < 0) + return false; + } + return true; + } + else { + if (Number.isNaN(parseNum(num)) == true) + return false; + else if (num >= 0) + return true; + else { + return false; + } + } +} + + +function isInt(num) { + /* + True: + + isInt(1) + isInt('1') + isInt(-1) + isInt('-1') + isInt(0) + isInt([1, -2, '3']) + isInt(1, 'a') -> true (be careful to enter multiple arguments as an array, otherwise it will only evaluate the first argument, as seen here) + + False: + + isInt(1.1) + isInt('1 ') + isInt('1 2') + isInt('abc') + isInt('abc123') + isInt('') + isInt(NaN) + isInt(Infinity) + isInt([1, 2, 3.3]) Returns false if any of the array elements are not an integer + + */ + + if (Array.isArray(num) == true) { + for (a = 0; a < num.length; a++) { + if (Number.isInteger(parseNum(num[a])) == false) { + return false; + } + } + return true; + } + else { + num = parseNum(num); + return Number.isInteger(num); + } +} + + +function isFloat(num) { + /* + + True: + + isFloat(1.1) + isFloat('1.1') + isFloat(-1.1) + isFloat('-1.1') + isFloat('.1') + isFloat('-.1') + isFloat([1.1, -1.2, '1.3']) + isFloat(Infinity) + isFloat([1.1, 'a']) -> true (be careful to enter multiple arguments as an array, otherwise it will only evaluate the first argument, as seen here) + + False: + + isFloat(1.0) + isFloat('1.0') + isFloat(0) + isFloat(1) + isFloat('1.1.1') + isFloat('') + isFloat('a') + isFloat('1.1a') + isFloat([1.1, 1.2, 3]) + isFloat(NaN) + + */ + if (Array.isArray(num) == true) { + for (a = 0; a < num.length; a++) { + num[a] = parseNum(num[a]); + if (Number.isInteger(num[a]) == true || Number.isNaN(num[a]) == true) { + return false; + } + } + return true; + } + else { + num = parseNum(num); + return !(Number.isInteger(num) || Number.isNaN(num)); + } +} + + +} + + +window.onpageshow = function() { + generate_table('Interface Support', -1); + $('#INPUT_HRES')[0].onchange(); + $('#INPUT_VRES')[0].onchange(); + $('#INPUT_F')[0].onchange(); + $('#COLOR_DEPTH_FORM')[0].onchange(); + $('#PIXEL_FORMAT_FORM')[0].onchange(); + $('#COMPRESSION_FORM')[0].onchange(); + $('#SCAN_FORM')[0].onchange(); + $('#MARGINS_FORM')[0].onchange(); + $('#TIMING_DROP')[0].onchange(); + calcMain(); +} \ No newline at end of file diff --git a/bandwidth/index.html b/bandwidth/index.html new file mode 100644 index 0000000..90c2f4a --- /dev/null +++ b/bandwidth/index.html @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + Bandwidth Calculator + + + + + +
+
+ + +
+ + + + + + + + + +
+ Video Format +
+ + + + +
+
+ + + + @ + + Hz +
+
+ Warning: Non-Standard Refresh Rate
+
+ The CTA-861 standard only defines timing parameters for this resolution at the following refresh rates:
+
    +
  • 50.000 Hz (VIC: 24)
  • +
  • 59.940 Hz (VIC: 25)
  • +
  • 60.000 Hz (VIC: 26)
  • +
+ This calculator uses the timing parameters from 60 Hz for non-standard refresh rates. +
+
+
+
+
+ + +
+ + + + + + + + + +
+ Advanced Settings +
+ + + + + + + + + + + +
+ Color Depth:
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ Pixel Format:
+
+
+
+
+
+
+
+ Compression:
+
+
+
+
+
+
+
+ Scan Type:
+
+
+
+
+
+ Margins:
+
+
+
+
+  % +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Timing Format:
+
+
+ VIC: 218 +
+ +
Front Porch:Sync Width:Back Porch:Total:
+ Hblank + +  px + +  px + +  px + + 1234 px +
+ Vblank + +  px + +  px + +  px + + 0 px +
+ (Odd) Vblank + +  px + +  px + +  px + + 0 px +
+
+
+
+ + +
+ + + + + + + + + +
+ Detailed Results +
+ + + + +
+ Video Format:
+
+ 4096 × 2560 (8:5 ratio), progressive scan
+ 120 Hz vertical refresh rate (frame rate)
+ 10 bpc (30 bit/px) color depth (1,073,741,824 colors)
+ YCBCR 4:4:4 pixel format
+ Uncompressed
+ CTA-861 timing (VIC: 63)
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Data Rate: + 37.75Gbit/s
+ Blanking Overhead: + 10.3%
+ Bandwidth: +
+ 8b/10b Encoding: + 45.30Gbit/s
+ 16b/18b Encoding: + 51.20Gbit/s
+ Character Rate ("Pixel Clock"): + 1.26GHz
+ Pixel Rate (Actual): + 1.26Gpx/s
+ Pixel Rate (Incl. Blank): + 1.58Gpx/s
+ Vertical Refresh Frequency (Actual): + 120.001Hz
+ Deviation: + 0.001%
+ Period: + 8.333ms
+ Horizontal Refresh Frequency: + 161.25kHz
Period:15.2µs
+
+
+ + +
+ + + + + + + + + +
+ Calculations +
+
+ $$\texttt{Pixels per frame:}$$ +
$$\texttt{4,096} \times \texttt{2,560 lines} = \texttt{10,485,760 px (10.5 Mpx)}$$
+ $$\texttt{Pixels per second:}$$ +
$$10\,485\,760\;\mathtt{px/frame} \times 120\;\mathtt{frame/s} = 1\,258\,291\,200\;\mathtt{px/s}\;(1.26\;\mathtt{Gpx/s})$$
+ $$\texttt{Bits per second:}$$ +
$$1\,258\,291\,200\;\mathtt{px/s} \times 30\;\mathtt{bit/px} = 37\,748\,736\,000\;\mathtt{bit/s}\;(37.7\;\mathtt{Gbit/s})$$
+


+ $$\texttt{Timing Formula:}$$ +
+ $$\texttt{Constants:}$$ + $$\texttt{H = 4,096 px}$$ + $$\texttt{V = 2,560 px}$$ + $$\texttt{F = 120 Hz}$$ +
+ $$\texttt{Formula:}$$ +
$$\texttt{V}_{\texttt{blank}} \texttt{ = } {{\texttt{V × V}_{\texttt{min}}} \over {{\texttt{1} \over \texttt{F}} - \texttt{V}_{\texttt{min}}}}$$
+
+
The timing parameters used for these calculations are defined by the CTA-861 standard. The definitions for this format are listed under Video Identification Code (VIC) #98, first defined in CTA-861-F (formerly CEA-861-F).
+
+
+ + +
+ + + + + + + + + +
+ Interface Support +
+ + +
+
+
+
+
+ +
+ +
+ + + + + + + + \ No newline at end of file diff --git a/bandwidth/jquery.csv.js b/bandwidth/jquery.csv.js new file mode 100644 index 0000000..9f8e5f7 --- /dev/null +++ b/bandwidth/jquery.csv.js @@ -0,0 +1,980 @@ +/** + * jQuery-csv (jQuery Plugin) + * + * This document is licensed as free software under the terms of the + * MIT License: http://www.opensource.org/licenses/mit-license.php + * + * Acknowledgements: + * The original design and influence to implement this library as a jquery + * plugin is influenced by jquery-json (http://code.google.com/p/jquery-json/). + * If you're looking to use native JSON.Stringify but want additional backwards + * compatibility for browsers that don't support it, I highly recommend you + * check it out. + * + * A special thanks goes out to rwk@acm.org for providing a lot of valuable + * feedback to the project including the core for the new FSM + * (Finite State Machine) parsers. If you're looking for a stable TSV parser + * be sure to take a look at jquery-tsv (http://code.google.com/p/jquery-tsv/). + + * For legal purposes I'll include the "NO WARRANTY EXPRESSED OR IMPLIED. + * USE AT YOUR OWN RISK.". Which, in 'layman's terms' means, by using this + * library you are accepting responsibility if it breaks your code. + * + * Legal jargon aside, I will do my best to provide a useful and stable core + * that can effectively be built on. + * + * Copyrighted 2012 by Evan Plaice. + */ + +RegExp.escape = function (s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); +}; + +(function () { + 'use strict'; + + var $; + + // to keep backwards compatibility + if (typeof jQuery !== 'undefined' && jQuery) { + $ = jQuery; + } else { + $ = {}; + } + + /** + * jQuery.csv.defaults + * Encapsulates the method paramater defaults for the CSV plugin module. + */ + + $.csv = { + defaults: { + separator: ',', + delimiter: '"', + headers: true + }, + + hooks: { + castToScalar: function (value, state) { + var hasDot = /\./; + if (isNaN(value)) { + return value; + } else { + if (hasDot.test(value)) { + return parseFloat(value); + } else { + var integer = parseInt(value); + if (isNaN(integer)) { + return null; + } else { + return integer; + } + } + } + } + }, + + parsers: { + parse: function (csv, options) { + // cache settings + var separator = options.separator; + var delimiter = options.delimiter; + + // set initial state if it's missing + if (!options.state.rowNum) { + options.state.rowNum = 1; + } + if (!options.state.colNum) { + options.state.colNum = 1; + } + + // clear initial state + var data = []; + var entry = []; + var state = 0; + var value = ''; + var exit = false; + + function endOfEntry () { + // reset the state + state = 0; + value = ''; + + // if 'start' hasn't been met, don't output + if (options.start && options.state.rowNum < options.start) { + // update global state + entry = []; + options.state.rowNum++; + options.state.colNum = 1; + return; + } + + if (options.onParseEntry === undefined) { + // onParseEntry hook not set + data.push(entry); + } else { + var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook + // false skips the row, configurable through a hook + if (hookVal !== false) { + data.push(hookVal); + } + } + // console.log('entry:' + entry); + + // cleanup + entry = []; + + // if 'end' is met, stop parsing + if (options.end && options.state.rowNum >= options.end) { + exit = true; + } + + // update global state + options.state.rowNum++; + options.state.colNum = 1; + } + + function endOfValue () { + if (options.onParseValue === undefined) { + // onParseValue hook not set + entry.push(value); + } else { + var hook = options.onParseValue(value, options.state); // onParseValue Hook + // false skips the row, configurable through a hook + if (hook !== false) { + entry.push(hook); + } + } + // console.log('value:' + value); + // reset the state + value = ''; + state = 0; + // update global state + options.state.colNum++; + } + + // escape regex-specific control chars + var escSeparator = RegExp.escape(separator); + var escDelimiter = RegExp.escape(delimiter); + + // compile the regEx str using the custom delimiter/separator + var match = /(D|S|\r\n|\n|\r|[^DS\r\n]+)/; + var matchSrc = match.source; + matchSrc = matchSrc.replace(/S/g, escSeparator); + matchSrc = matchSrc.replace(/D/g, escDelimiter); + match = new RegExp(matchSrc, 'gm'); + + // put on your fancy pants... + // process control chars individually, use look-ahead on non-control chars + csv.replace(match, function (m0) { + if (exit) { + return; + } + switch (state) { + // the start of a value + case 0: + // null last value + if (m0 === separator) { + value += ''; + endOfValue(); + break; + } + // opening delimiter + if (m0 === delimiter) { + state = 1; + break; + } + // null last value + if (/^(\r\n|\n|\r)$/.test(m0)) { + endOfValue(); + endOfEntry(); + break; + } + // un-delimited value + value += m0; + state = 3; + break; + + // delimited input + case 1: + // second delimiter? check further + if (m0 === delimiter) { + state = 2; + break; + } + // delimited data + value += m0; + state = 1; + break; + + // delimiter found in delimited input + case 2: + // escaped delimiter? + if (m0 === delimiter) { + value += m0; + state = 1; + break; + } + // null value + if (m0 === separator) { + endOfValue(); + break; + } + // end of entry + if (/^(\r\n|\n|\r)$/.test(m0)) { + endOfValue(); + endOfEntry(); + break; + } + // broken paser? + throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + + // un-delimited input + case 3: + // null last value + if (m0 === separator) { + endOfValue(); + break; + } + // end of entry + if (/^(\r\n|\n|\r)$/.test(m0)) { + endOfValue(); + endOfEntry(); + break; + } + if (m0 === delimiter) { + // non-compliant data + throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + } + // broken parser? + throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + default: + // shenanigans + throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + } + // console.log('val:' + m0 + ' state:' + state); + }); + + // submit the last entry + // ignore null last line + if (entry.length !== 0) { + endOfValue(); + endOfEntry(); + } + + return data; + }, + + // a csv-specific line splitter + splitLines: function (csv, options) { + if (!csv) { + return undefined; + } + + options = options || {}; + + // cache settings + var separator = options.separator || $.csv.defaults.separator; + var delimiter = options.delimiter || $.csv.defaults.delimiter; + + // set initial state if it's missing + options.state = options.state || {}; + if (!options.state.rowNum) { + options.state.rowNum = 1; + } + + // clear initial state + var entries = []; + var state = 0; + var entry = ''; + var exit = false; + + function endOfLine () { + // reset the state + state = 0; + + // if 'start' hasn't been met, don't output + if (options.start && options.state.rowNum < options.start) { + // update global state + entry = ''; + options.state.rowNum++; + return; + } + + if (options.onParseEntry === undefined) { + // onParseEntry hook not set + entries.push(entry); + } else { + var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook + // false skips the row, configurable through a hook + if (hookVal !== false) { + entries.push(hookVal); + } + } + + // cleanup + entry = ''; + + // if 'end' is met, stop parsing + if (options.end && options.state.rowNum >= options.end) { + exit = true; + } + + // update global state + options.state.rowNum++; + } + + // escape regex-specific control chars + var escSeparator = RegExp.escape(separator); + var escDelimiter = RegExp.escape(delimiter); + + // compile the regEx str using the custom delimiter/separator + var match = /(D|S|\n|\r|[^DS\r\n]+)/; + var matchSrc = match.source; + matchSrc = matchSrc.replace(/S/g, escSeparator); + matchSrc = matchSrc.replace(/D/g, escDelimiter); + match = new RegExp(matchSrc, 'gm'); + + // put on your fancy pants... + // process control chars individually, use look-ahead on non-control chars + csv.replace(match, function (m0) { + if (exit) { + return; + } + switch (state) { + // the start of a value/entry + case 0: + // null value + if (m0 === separator) { + entry += m0; + state = 0; + break; + } + // opening delimiter + if (m0 === delimiter) { + entry += m0; + state = 1; + break; + } + // end of line + if (m0 === '\n') { + endOfLine(); + break; + } + // phantom carriage return + if (/^\r$/.test(m0)) { + break; + } + // un-delimit value + entry += m0; + state = 3; + break; + + // delimited input + case 1: + // second delimiter? check further + if (m0 === delimiter) { + entry += m0; + state = 2; + break; + } + // delimited data + entry += m0; + state = 1; + break; + + // delimiter found in delimited input + case 2: + // escaped delimiter? + var prevChar = entry.substr(entry.length - 1); + if (m0 === delimiter && prevChar === delimiter) { + entry += m0; + state = 1; + break; + } + // end of value + if (m0 === separator) { + entry += m0; + state = 0; + break; + } + // end of line + if (m0 === '\n') { + endOfLine(); + break; + } + // phantom carriage return + if (m0 === '\r') { + break; + } + // broken paser? + throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']'); + + // un-delimited input + case 3: + // null value + if (m0 === separator) { + entry += m0; + state = 0; + break; + } + // end of line + if (m0 === '\n') { + endOfLine(); + break; + } + // phantom carriage return + if (m0 === '\r') { + break; + } + // non-compliant data + if (m0 === delimiter) { + throw new Error('CSVDataError: Illegal quote [Row:' + options.state.rowNum + ']'); + } + // broken parser? + throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']'); + default: + // shenanigans + throw new Error('CSVDataError: Unknown state [Row:' + options.state.rowNum + ']'); + } + // console.log('val:' + m0 + ' state:' + state); + }); + + // submit the last entry + // ignore null last line + if (entry !== '') { + endOfLine(); + } + + return entries; + }, + + // a csv entry parser + parseEntry: function (csv, options) { + // cache settings + var separator = options.separator; + var delimiter = options.delimiter; + + // set initial state if it's missing + if (!options.state.rowNum) { + options.state.rowNum = 1; + } + if (!options.state.colNum) { + options.state.colNum = 1; + } + + // clear initial state + var entry = []; + var state = 0; + var value = ''; + + function endOfValue () { + if (options.onParseValue === undefined) { + // onParseValue hook not set + entry.push(value); + } else { + var hook = options.onParseValue(value, options.state); // onParseValue Hook + // false skips the value, configurable through a hook + if (hook !== false) { + entry.push(hook); + } + } + // reset the state + value = ''; + state = 0; + // update global state + options.state.colNum++; + } + + // checked for a cached regEx first + if (!options.match) { + // escape regex-specific control chars + var escSeparator = RegExp.escape(separator); + var escDelimiter = RegExp.escape(delimiter); + + // compile the regEx str using the custom delimiter/separator + var match = /(D|S|\n|\r|[^DS\r\n]+)/; + var matchSrc = match.source; + matchSrc = matchSrc.replace(/S/g, escSeparator); + matchSrc = matchSrc.replace(/D/g, escDelimiter); + options.match = new RegExp(matchSrc, 'gm'); + } + + // put on your fancy pants... + // process control chars individually, use look-ahead on non-control chars + csv.replace(options.match, function (m0) { + switch (state) { + // the start of a value + case 0: + // null last value + if (m0 === separator) { + value += ''; + endOfValue(); + break; + } + // opening delimiter + if (m0 === delimiter) { + state = 1; + break; + } + // skip un-delimited new-lines + if (m0 === '\n' || m0 === '\r') { + break; + } + // un-delimited value + value += m0; + state = 3; + break; + + // delimited input + case 1: + // second delimiter? check further + if (m0 === delimiter) { + state = 2; + break; + } + // delimited data + value += m0; + state = 1; + break; + + // delimiter found in delimited input + case 2: + // escaped delimiter? + if (m0 === delimiter) { + value += m0; + state = 1; + break; + } + // null value + if (m0 === separator) { + endOfValue(); + break; + } + // skip un-delimited new-lines + if (m0 === '\n' || m0 === '\r') { + break; + } + // broken paser? + throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + + // un-delimited input + case 3: + // null last value + if (m0 === separator) { + endOfValue(); + break; + } + // skip un-delimited new-lines + if (m0 === '\n' || m0 === '\r') { + break; + } + // non-compliant data + if (m0 === delimiter) { + throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + } + // broken parser? + throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + default: + // shenanigans + throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']'); + } + // console.log('val:' + m0 + ' state:' + state); + }); + + // submit the last value + endOfValue(); + + return entry; + } + }, + + helpers: { + + /** + * $.csv.helpers.collectPropertyNames(objectsArray) + * Collects all unique property names from all passed objects. + * + * @param {Array} objects Objects to collect properties from. + * + * Returns an array of property names (array will be empty, + * if objects have no own properties). + */ + collectPropertyNames: function (objects) { + var o = []; + var propName = []; + var props = []; + for (o in objects) { + for (propName in objects[o]) { + if ((objects[o].hasOwnProperty(propName)) && + (props.indexOf(propName) < 0) && + (typeof objects[o][propName] !== 'function')) { + props.push(propName); + } + } + } + return props; + } + }, + + /** + * $.csv.toArray(csv) + * Converts a CSV entry string to a javascript array. + * + * @param {Array} csv The string containing the CSV data. + * @param {Object} [options] An object containing user-defined options. + * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). + * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). + * + * This method deals with simple CSV strings only. It's useful if you only + * need to parse a single entry. If you need to parse more than one line, + * use $.csv2Array instead. + */ + toArray: function (csv, options, callback) { + options = (options !== undefined ? options : {}); + var config = {}; + config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false); + config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; + config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; + var state = (options.state !== undefined ? options.state : {}); + + // setup + options = { + delimiter: config.delimiter, + separator: config.separator, + onParseEntry: options.onParseEntry, + onParseValue: options.onParseValue, + state: state + }; + + var entry = $.csv.parsers.parseEntry(csv, options); + + // push the value to a callback if one is defined + if (!config.callback) { + return entry; + } else { + config.callback('', entry); + } + }, + + /** + * $.csv.toArrays(csv) + * Converts a CSV string to a javascript array. + * + * @param {String} csv The string containing the raw CSV data. + * @param {Object} [options] An object containing user-defined options. + * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). + * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). + * + * This method deals with multi-line CSV. The breakdown is simple. The first + * dimension of the array represents the line (or entry/row) while the second + * dimension contains the values (or values/columns). + */ + toArrays: function (csv, options, callback) { + options = (options !== undefined ? options : {}); + var config = {}; + config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false); + config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; + config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; + + // setup + var data = []; + options = { + delimiter: config.delimiter, + separator: config.separator, + onPreParse: options.onPreParse, + onParseEntry: options.onParseEntry, + onParseValue: options.onParseValue, + onPostParse: options.onPostParse, + start: options.start, + end: options.end, + state: { + rowNum: 1, + colNum: 1 + } + }; + + // onPreParse hook + if (options.onPreParse !== undefined) { + options.onPreParse(csv, options.state); + } + + // parse the data + data = $.csv.parsers.parse(csv, options); + + // onPostParse hook + if (options.onPostParse !== undefined) { + options.onPostParse(data, options.state); + } + + // push the value to a callback if one is defined + if (!config.callback) { + return data; + } else { + config.callback('', data); + } + }, + + /** + * $.csv.toObjects(csv) + * Converts a CSV string to a javascript object. + * @param {String} csv The string containing the raw CSV data. + * @param {Object} [options] An object containing user-defined options. + * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). + * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). + * @param {Boolean} [headers] Indicates whether the data contains a header line. Defaults to true. + * + * This method deals with multi-line CSV strings. Where the headers line is + * used as the key for each value per entry. + */ + toObjects: function (csv, options, callback) { + options = (options !== undefined ? options : {}); + var config = {}; + config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false); + config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; + config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; + config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers; + options.start = 'start' in options ? options.start : 1; + + // account for headers + if (config.headers) { + options.start++; + } + if (options.end && config.headers) { + options.end++; + } + + // setup + var lines = []; + var data = []; + + options = { + delimiter: config.delimiter, + separator: config.separator, + onPreParse: options.onPreParse, + onParseEntry: options.onParseEntry, + onParseValue: options.onParseValue, + onPostParse: options.onPostParse, + start: options.start, + end: options.end, + state: { + rowNum: 1, + colNum: 1 + }, + match: false, + transform: options.transform + }; + + // fetch the headers + var headerOptions = { + delimiter: config.delimiter, + separator: config.separator, + start: 1, + end: 1, + state: { + rowNum: 1, + colNum: 1 + } + }; + + // onPreParse hook + if (options.onPreParse !== undefined) { + options.onPreParse(csv, options.state); + } + + // parse the csv + var headerLine = $.csv.parsers.splitLines(csv, headerOptions); + var headers = $.csv.toArray(headerLine[0], options); + + // fetch the data + lines = $.csv.parsers.splitLines(csv, options); + + // reset the state for re-use + options.state.colNum = 1; + if (headers) { + options.state.rowNum = 2; + } else { + options.state.rowNum = 1; + } + + // convert data to objects + for (var i = 0, len = lines.length; i < len; i++) { + var entry = $.csv.toArray(lines[i], options); + var object = {}; + for (var j = 0; j < headers.length; j++) { + object[headers[j]] = entry[j]; + } + if (options.transform !== undefined) { + data.push(options.transform.call(undefined, object)); + } else { + data.push(object); + } + + // update row state + options.state.rowNum++; + } + + // onPostParse hook + if (options.onPostParse !== undefined) { + options.onPostParse(data, options.state); + } + + // push the value to a callback if one is defined + if (!config.callback) { + return data; + } else { + config.callback('', data); + } + }, + + /** + * $.csv.fromArrays(arrays) + * Converts a javascript array to a CSV String. + * + * @param {Array} arrays An array containing an array of CSV entries. + * @param {Object} [options] An object containing user-defined options. + * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). + * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). + * + * This method generates a CSV file from an array of arrays (representing entries). + */ + fromArrays: function (arrays, options, callback) { + options = (options !== undefined ? options : {}); + var config = {}; + config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false); + config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; + config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; + + var output = ''; + var line; + var lineValues; + var i; + var j; + + for (i = 0; i < arrays.length; i++) { + line = arrays[i]; + lineValues = []; + for (j = 0; j < line.length; j++) { + var strValue = (line[j] === undefined || line[j] === null) ? '' : line[j].toString(); + if (strValue.indexOf(config.delimiter) > -1) { + strValue = strValue.replace(new RegExp(config.delimiter, 'g'), config.delimiter + config.delimiter); + } + + var escMatcher = '\n|\r|S|D'; + escMatcher = escMatcher.replace('S', config.separator); + escMatcher = escMatcher.replace('D', config.delimiter); + + if (strValue.search(escMatcher) > -1) { + strValue = config.delimiter + strValue + config.delimiter; + } + lineValues.push(strValue); + } + output += lineValues.join(config.separator) + '\n'; + } + + // push the value to a callback if one is defined + if (!config.callback) { + return output; + } else { + config.callback('', output); + } + }, + + /** + * $.csv.fromObjects(objects) + * Converts a javascript dictionary to a CSV string. + * + * @param {Object} objects An array of objects containing the data. + * @param {Object} [options] An object containing user-defined options. + * @param {Character} [separator] An override for the separator character. Defaults to a comma(,). + * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote("). + * @param {Character} [sortOrder] Sort order of columns (named after + * object properties). Use 'alpha' for alphabetic. Default is 'declare', + * which means, that properties will _probably_ appear in order they were + * declared for the object. But without any guarantee. + * @param {Character or Array} [manualOrder] Manually order columns. May be + * a strin in a same csv format as an output or an array of header names + * (array items won't be parsed). All the properties, not present in + * `manualOrder` will be appended to the end in accordance with `sortOrder` + * option. So the `manualOrder` always takes preference, if present. + * + * This method generates a CSV file from an array of objects (name:value pairs). + * It starts by detecting the headers and adding them as the first line of + * the CSV file, followed by a structured dump of the data. + */ + fromObjects: function (objects, options, callback) { + options = (options !== undefined ? options : {}); + var config = {}; + config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false); + config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator; + config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter; + config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers; + config.sortOrder = 'sortOrder' in options ? options.sortOrder : 'declare'; + config.manualOrder = 'manualOrder' in options ? options.manualOrder : []; + config.transform = options.transform; + + if (typeof config.manualOrder === 'string') { + config.manualOrder = $.csv.toArray(config.manualOrder, config); + } + + if (config.transform !== undefined) { + var origObjects = objects; + objects = []; + + var i; + for (i = 0; i < origObjects.length; i++) { + objects.push(config.transform.call(undefined, origObjects[i])); + } + } + + var props = $.csv.helpers.collectPropertyNames(objects); + + if (config.sortOrder === 'alpha') { + props.sort(); + } // else {} - nothing to do for 'declare' order + + if (config.manualOrder.length > 0) { + var propsManual = [].concat(config.manualOrder); + let p; + for (p = 0; p < props.length; p++) { + if (propsManual.indexOf(props[p]) < 0) { + propsManual.push(props[p]); + } + } + props = propsManual; + } + + var o, p, line, output, propName; + if (config.headers) { + output.push(props); + } + + for (o = 0; o < objects.length; o++) { + line = []; + for (p = 0; p < props.length; p++) { + propName = props[p]; + if (propName in objects[o] && typeof objects[o][propName] !== 'function') { + line.push(objects[o][propName]); + } else { + line.push(''); + } + } + output.push(line); + } + + // push the value to a callback if one is defined + return $.csv.fromArrays(output, options, config.callback); + } + }; + + // Maintenance code to maintain backward-compatibility + // Will be removed in release 1.0 + $.csvEntry2Array = $.csv.toArray; + $.csv2Array = $.csv.toArrays; + $.csv2Dictionary = $.csv.toObjects; + + // CommonJS module is defined + if (typeof module !== 'undefined' && module.exports) { + module.exports = $.csv; + } +}).call(this); \ No newline at end of file diff --git a/diu/diuJS.js b/diu/diuJS.js index e956593..0819bcf 100644 --- a/diu/diuJS.js +++ b/diu/diuJS.js @@ -85,6 +85,7 @@ // Returns false if input is a non-integer number or NaN function isInt(num) { + num = parseNum(num); if (Array.isArray(num) == true) { for (a = 0; a < num.length; a++) { if (Number.isInteger(num[a]) == false) { @@ -97,6 +98,22 @@ function isInt(num) { return Number.isInteger(num); } + +function isFloat(num) { + num = parseNum(num); + if (Array.isArray(num) == true) { + for (a = 0; a < num.length; a++) { + if (Number.isInteger(num[a]) == true || Number.isNaN(num[a]) == true) { + return false; + } + return true; + } + } + else + return !(Number.isInteger(num) || Number.isNaN(num)); +} + + // Returns false if input is not a positive number (zero, negative number, or NaN) function isPositive(num) { if (Array.isArray(num) == true) { @@ -113,6 +130,30 @@ function isPositive(num) { return false; else if (num > 0) return true; + else { + return false; + } + } +} + +function isNonNegative(num) { + if (Array.isArray(num) == true) { + for (a = 0; a < num.length; a++) { + if (Number.isNaN(parseNum(num[a])) == true) + return false; + else if (num[a] < 0) + return false; + } + return true; + } + else { + if (Number.isNaN(parseNum(num)) == true) + return false; + else if (num >= 0) + return true; + else { + return false; + } } } diff --git a/index.html b/index.html index af5cc97..f8d9371 100644 --- a/index.html +++ b/index.html @@ -11,8 +11,8 @@ } body { - font-family: "Consolas", "Inconsolata", "Lucida Console", Monospace; - font-size: 15px; + font-family: "Consolas", "Inconsolata", "Lucida Console", Monospace; + font-size: 15px; line-height: 120%; min-height: 100%; }