pattern.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. /* Copyright 2012 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. import {
  16. assert,
  17. FormatError,
  18. info,
  19. shadow,
  20. unreachable,
  21. UNSUPPORTED_FEATURES,
  22. Util,
  23. warn,
  24. } from "../shared/util.js";
  25. import { BaseStream } from "./base_stream.js";
  26. import { ColorSpace } from "./colorspace.js";
  27. import { MissingDataException } from "./core_utils.js";
  28. const ShadingType = {
  29. FUNCTION_BASED: 1,
  30. AXIAL: 2,
  31. RADIAL: 3,
  32. FREE_FORM_MESH: 4,
  33. LATTICE_FORM_MESH: 5,
  34. COONS_PATCH_MESH: 6,
  35. TENSOR_PATCH_MESH: 7,
  36. };
  37. class Pattern {
  38. constructor() {
  39. unreachable("Cannot initialize Pattern.");
  40. }
  41. static parseShading(
  42. shading,
  43. xref,
  44. res,
  45. handler,
  46. pdfFunctionFactory,
  47. localColorSpaceCache
  48. ) {
  49. const dict = shading instanceof BaseStream ? shading.dict : shading;
  50. const type = dict.get("ShadingType");
  51. try {
  52. switch (type) {
  53. case ShadingType.AXIAL:
  54. case ShadingType.RADIAL:
  55. return new RadialAxialShading(
  56. dict,
  57. xref,
  58. res,
  59. pdfFunctionFactory,
  60. localColorSpaceCache
  61. );
  62. case ShadingType.FREE_FORM_MESH:
  63. case ShadingType.LATTICE_FORM_MESH:
  64. case ShadingType.COONS_PATCH_MESH:
  65. case ShadingType.TENSOR_PATCH_MESH:
  66. return new MeshShading(
  67. shading,
  68. xref,
  69. res,
  70. pdfFunctionFactory,
  71. localColorSpaceCache
  72. );
  73. default:
  74. throw new FormatError("Unsupported ShadingType: " + type);
  75. }
  76. } catch (ex) {
  77. if (ex instanceof MissingDataException) {
  78. throw ex;
  79. }
  80. handler.send("UnsupportedFeature", {
  81. featureId: UNSUPPORTED_FEATURES.shadingPattern,
  82. });
  83. warn(ex);
  84. return new DummyShading();
  85. }
  86. }
  87. }
  88. class BaseShading {
  89. // A small number to offset the first/last color stops so we can insert ones
  90. // to support extend. Number.MIN_VALUE is too small and breaks the extend.
  91. static get SMALL_NUMBER() {
  92. return shadow(this, "SMALL_NUMBER", 1e-6);
  93. }
  94. constructor() {
  95. if (this.constructor === BaseShading) {
  96. unreachable("Cannot initialize BaseShading.");
  97. }
  98. }
  99. getIR() {
  100. unreachable("Abstract method `getIR` called.");
  101. }
  102. }
  103. // Radial and axial shading have very similar implementations
  104. // If needed, the implementations can be broken into two classes.
  105. class RadialAxialShading extends BaseShading {
  106. constructor(dict, xref, resources, pdfFunctionFactory, localColorSpaceCache) {
  107. super();
  108. this.coordsArr = dict.getArray("Coords");
  109. this.shadingType = dict.get("ShadingType");
  110. const cs = ColorSpace.parse({
  111. cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
  112. xref,
  113. resources,
  114. pdfFunctionFactory,
  115. localColorSpaceCache,
  116. });
  117. const bbox = dict.getArray("BBox");
  118. if (Array.isArray(bbox) && bbox.length === 4) {
  119. this.bbox = Util.normalizeRect(bbox);
  120. } else {
  121. this.bbox = null;
  122. }
  123. let t0 = 0.0,
  124. t1 = 1.0;
  125. if (dict.has("Domain")) {
  126. const domainArr = dict.getArray("Domain");
  127. t0 = domainArr[0];
  128. t1 = domainArr[1];
  129. }
  130. let extendStart = false,
  131. extendEnd = false;
  132. if (dict.has("Extend")) {
  133. const extendArr = dict.getArray("Extend");
  134. extendStart = extendArr[0];
  135. extendEnd = extendArr[1];
  136. }
  137. if (
  138. this.shadingType === ShadingType.RADIAL &&
  139. (!extendStart || !extendEnd)
  140. ) {
  141. // Radial gradient only currently works if either circle is fully within
  142. // the other circle.
  143. const [x1, y1, r1, x2, y2, r2] = this.coordsArr;
  144. const distance = Math.hypot(x1 - x2, y1 - y2);
  145. if (r1 <= r2 + distance && r2 <= r1 + distance) {
  146. warn("Unsupported radial gradient.");
  147. }
  148. }
  149. this.extendStart = extendStart;
  150. this.extendEnd = extendEnd;
  151. const fnObj = dict.getRaw("Function");
  152. const fn = pdfFunctionFactory.createFromArray(fnObj);
  153. // 10 samples seems good enough for now, but probably won't work
  154. // if there are sharp color changes. Ideally, we would implement
  155. // the spec faithfully and add lossless optimizations.
  156. const NUMBER_OF_SAMPLES = 10;
  157. const step = (t1 - t0) / NUMBER_OF_SAMPLES;
  158. const colorStops = (this.colorStops = []);
  159. // Protect against bad domains.
  160. if (t0 >= t1 || step <= 0) {
  161. // Acrobat doesn't seem to handle these cases so we'll ignore for
  162. // now.
  163. info("Bad shading domain.");
  164. return;
  165. }
  166. const color = new Float32Array(cs.numComps),
  167. ratio = new Float32Array(1);
  168. let rgbColor;
  169. for (let i = 0; i <= NUMBER_OF_SAMPLES; i++) {
  170. ratio[0] = t0 + i * step;
  171. fn(ratio, 0, color, 0);
  172. rgbColor = cs.getRgb(color, 0);
  173. const cssColor = Util.makeHexColor(rgbColor[0], rgbColor[1], rgbColor[2]);
  174. colorStops.push([i / NUMBER_OF_SAMPLES, cssColor]);
  175. }
  176. let background = "transparent";
  177. if (dict.has("Background")) {
  178. rgbColor = cs.getRgb(dict.get("Background"), 0);
  179. background = Util.makeHexColor(rgbColor[0], rgbColor[1], rgbColor[2]);
  180. }
  181. if (!extendStart) {
  182. // Insert a color stop at the front and offset the first real color stop
  183. // so it doesn't conflict with the one we insert.
  184. colorStops.unshift([0, background]);
  185. colorStops[1][0] += BaseShading.SMALL_NUMBER;
  186. }
  187. if (!extendEnd) {
  188. // Same idea as above in extendStart but for the end.
  189. colorStops.at(-1)[0] -= BaseShading.SMALL_NUMBER;
  190. colorStops.push([1, background]);
  191. }
  192. this.colorStops = colorStops;
  193. }
  194. getIR() {
  195. const coordsArr = this.coordsArr;
  196. const shadingType = this.shadingType;
  197. let type, p0, p1, r0, r1;
  198. if (shadingType === ShadingType.AXIAL) {
  199. p0 = [coordsArr[0], coordsArr[1]];
  200. p1 = [coordsArr[2], coordsArr[3]];
  201. r0 = null;
  202. r1 = null;
  203. type = "axial";
  204. } else if (shadingType === ShadingType.RADIAL) {
  205. p0 = [coordsArr[0], coordsArr[1]];
  206. p1 = [coordsArr[3], coordsArr[4]];
  207. r0 = coordsArr[2];
  208. r1 = coordsArr[5];
  209. type = "radial";
  210. } else {
  211. unreachable(`getPattern type unknown: ${shadingType}`);
  212. }
  213. return ["RadialAxial", type, this.bbox, this.colorStops, p0, p1, r0, r1];
  214. }
  215. }
  216. // All mesh shadings. For now, they will be presented as set of the triangles
  217. // to be drawn on the canvas and rgb color for each vertex.
  218. class MeshStreamReader {
  219. constructor(stream, context) {
  220. this.stream = stream;
  221. this.context = context;
  222. this.buffer = 0;
  223. this.bufferLength = 0;
  224. const numComps = context.numComps;
  225. this.tmpCompsBuf = new Float32Array(numComps);
  226. const csNumComps = context.colorSpace.numComps;
  227. this.tmpCsCompsBuf = context.colorFn
  228. ? new Float32Array(csNumComps)
  229. : this.tmpCompsBuf;
  230. }
  231. get hasData() {
  232. if (this.stream.end) {
  233. return this.stream.pos < this.stream.end;
  234. }
  235. if (this.bufferLength > 0) {
  236. return true;
  237. }
  238. const nextByte = this.stream.getByte();
  239. if (nextByte < 0) {
  240. return false;
  241. }
  242. this.buffer = nextByte;
  243. this.bufferLength = 8;
  244. return true;
  245. }
  246. readBits(n) {
  247. let buffer = this.buffer;
  248. let bufferLength = this.bufferLength;
  249. if (n === 32) {
  250. if (bufferLength === 0) {
  251. return (
  252. ((this.stream.getByte() << 24) |
  253. (this.stream.getByte() << 16) |
  254. (this.stream.getByte() << 8) |
  255. this.stream.getByte()) >>>
  256. 0
  257. );
  258. }
  259. buffer =
  260. (buffer << 24) |
  261. (this.stream.getByte() << 16) |
  262. (this.stream.getByte() << 8) |
  263. this.stream.getByte();
  264. const nextByte = this.stream.getByte();
  265. this.buffer = nextByte & ((1 << bufferLength) - 1);
  266. return (
  267. ((buffer << (8 - bufferLength)) |
  268. ((nextByte & 0xff) >> bufferLength)) >>>
  269. 0
  270. );
  271. }
  272. if (n === 8 && bufferLength === 0) {
  273. return this.stream.getByte();
  274. }
  275. while (bufferLength < n) {
  276. buffer = (buffer << 8) | this.stream.getByte();
  277. bufferLength += 8;
  278. }
  279. bufferLength -= n;
  280. this.bufferLength = bufferLength;
  281. this.buffer = buffer & ((1 << bufferLength) - 1);
  282. return buffer >> bufferLength;
  283. }
  284. align() {
  285. this.buffer = 0;
  286. this.bufferLength = 0;
  287. }
  288. readFlag() {
  289. return this.readBits(this.context.bitsPerFlag);
  290. }
  291. readCoordinate() {
  292. const bitsPerCoordinate = this.context.bitsPerCoordinate;
  293. const xi = this.readBits(bitsPerCoordinate);
  294. const yi = this.readBits(bitsPerCoordinate);
  295. const decode = this.context.decode;
  296. const scale =
  297. bitsPerCoordinate < 32
  298. ? 1 / ((1 << bitsPerCoordinate) - 1)
  299. : 2.3283064365386963e-10; // 2 ^ -32
  300. return [
  301. xi * scale * (decode[1] - decode[0]) + decode[0],
  302. yi * scale * (decode[3] - decode[2]) + decode[2],
  303. ];
  304. }
  305. readComponents() {
  306. const numComps = this.context.numComps;
  307. const bitsPerComponent = this.context.bitsPerComponent;
  308. const scale =
  309. bitsPerComponent < 32
  310. ? 1 / ((1 << bitsPerComponent) - 1)
  311. : 2.3283064365386963e-10; // 2 ^ -32
  312. const decode = this.context.decode;
  313. const components = this.tmpCompsBuf;
  314. for (let i = 0, j = 4; i < numComps; i++, j += 2) {
  315. const ci = this.readBits(bitsPerComponent);
  316. components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j];
  317. }
  318. const color = this.tmpCsCompsBuf;
  319. if (this.context.colorFn) {
  320. this.context.colorFn(components, 0, color, 0);
  321. }
  322. return this.context.colorSpace.getRgb(color, 0);
  323. }
  324. }
  325. const getB = (function getBClosure() {
  326. function buildB(count) {
  327. const lut = [];
  328. for (let i = 0; i <= count; i++) {
  329. const t = i / count,
  330. t_ = 1 - t;
  331. lut.push(
  332. new Float32Array([
  333. t_ * t_ * t_,
  334. 3 * t * t_ * t_,
  335. 3 * t * t * t_,
  336. t * t * t,
  337. ])
  338. );
  339. }
  340. return lut;
  341. }
  342. const cache = [];
  343. return function (count) {
  344. if (!cache[count]) {
  345. cache[count] = buildB(count);
  346. }
  347. return cache[count];
  348. };
  349. })();
  350. class MeshShading extends BaseShading {
  351. static get MIN_SPLIT_PATCH_CHUNKS_AMOUNT() {
  352. return shadow(this, "MIN_SPLIT_PATCH_CHUNKS_AMOUNT", 3);
  353. }
  354. static get MAX_SPLIT_PATCH_CHUNKS_AMOUNT() {
  355. return shadow(this, "MAX_SPLIT_PATCH_CHUNKS_AMOUNT", 20);
  356. }
  357. // Count of triangles per entire mesh bounds.
  358. static get TRIANGLE_DENSITY() {
  359. return shadow(this, "TRIANGLE_DENSITY", 20);
  360. }
  361. constructor(
  362. stream,
  363. xref,
  364. resources,
  365. pdfFunctionFactory,
  366. localColorSpaceCache
  367. ) {
  368. super();
  369. if (!(stream instanceof BaseStream)) {
  370. throw new FormatError("Mesh data is not a stream");
  371. }
  372. const dict = stream.dict;
  373. this.shadingType = dict.get("ShadingType");
  374. const bbox = dict.getArray("BBox");
  375. if (Array.isArray(bbox) && bbox.length === 4) {
  376. this.bbox = Util.normalizeRect(bbox);
  377. } else {
  378. this.bbox = null;
  379. }
  380. const cs = ColorSpace.parse({
  381. cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
  382. xref,
  383. resources,
  384. pdfFunctionFactory,
  385. localColorSpaceCache,
  386. });
  387. this.background = dict.has("Background")
  388. ? cs.getRgb(dict.get("Background"), 0)
  389. : null;
  390. const fnObj = dict.getRaw("Function");
  391. const fn = fnObj ? pdfFunctionFactory.createFromArray(fnObj) : null;
  392. this.coords = [];
  393. this.colors = [];
  394. this.figures = [];
  395. const decodeContext = {
  396. bitsPerCoordinate: dict.get("BitsPerCoordinate"),
  397. bitsPerComponent: dict.get("BitsPerComponent"),
  398. bitsPerFlag: dict.get("BitsPerFlag"),
  399. decode: dict.getArray("Decode"),
  400. colorFn: fn,
  401. colorSpace: cs,
  402. numComps: fn ? 1 : cs.numComps,
  403. };
  404. const reader = new MeshStreamReader(stream, decodeContext);
  405. let patchMesh = false;
  406. switch (this.shadingType) {
  407. case ShadingType.FREE_FORM_MESH:
  408. this._decodeType4Shading(reader);
  409. break;
  410. case ShadingType.LATTICE_FORM_MESH:
  411. const verticesPerRow = dict.get("VerticesPerRow") | 0;
  412. if (verticesPerRow < 2) {
  413. throw new FormatError("Invalid VerticesPerRow");
  414. }
  415. this._decodeType5Shading(reader, verticesPerRow);
  416. break;
  417. case ShadingType.COONS_PATCH_MESH:
  418. this._decodeType6Shading(reader);
  419. patchMesh = true;
  420. break;
  421. case ShadingType.TENSOR_PATCH_MESH:
  422. this._decodeType7Shading(reader);
  423. patchMesh = true;
  424. break;
  425. default:
  426. unreachable("Unsupported mesh type.");
  427. break;
  428. }
  429. if (patchMesh) {
  430. // Dirty bounds calculation, to determine how dense the triangles will be.
  431. this._updateBounds();
  432. for (let i = 0, ii = this.figures.length; i < ii; i++) {
  433. this._buildFigureFromPatch(i);
  434. }
  435. }
  436. // Calculate bounds.
  437. this._updateBounds();
  438. this._packData();
  439. }
  440. _decodeType4Shading(reader) {
  441. const coords = this.coords;
  442. const colors = this.colors;
  443. const operators = [];
  444. const ps = []; // not maintaining cs since that will match ps
  445. let verticesLeft = 0; // assuming we have all data to start a new triangle
  446. while (reader.hasData) {
  447. const f = reader.readFlag();
  448. const coord = reader.readCoordinate();
  449. const color = reader.readComponents();
  450. if (verticesLeft === 0) {
  451. // ignoring flags if we started a triangle
  452. if (!(0 <= f && f <= 2)) {
  453. throw new FormatError("Unknown type4 flag");
  454. }
  455. switch (f) {
  456. case 0:
  457. verticesLeft = 3;
  458. break;
  459. case 1:
  460. ps.push(ps.at(-2), ps.at(-1));
  461. verticesLeft = 1;
  462. break;
  463. case 2:
  464. ps.push(ps.at(-3), ps.at(-1));
  465. verticesLeft = 1;
  466. break;
  467. }
  468. operators.push(f);
  469. }
  470. ps.push(coords.length);
  471. coords.push(coord);
  472. colors.push(color);
  473. verticesLeft--;
  474. reader.align();
  475. }
  476. this.figures.push({
  477. type: "triangles",
  478. coords: new Int32Array(ps),
  479. colors: new Int32Array(ps),
  480. });
  481. }
  482. _decodeType5Shading(reader, verticesPerRow) {
  483. const coords = this.coords;
  484. const colors = this.colors;
  485. const ps = []; // not maintaining cs since that will match ps
  486. while (reader.hasData) {
  487. const coord = reader.readCoordinate();
  488. const color = reader.readComponents();
  489. ps.push(coords.length);
  490. coords.push(coord);
  491. colors.push(color);
  492. }
  493. this.figures.push({
  494. type: "lattice",
  495. coords: new Int32Array(ps),
  496. colors: new Int32Array(ps),
  497. verticesPerRow,
  498. });
  499. }
  500. _decodeType6Shading(reader) {
  501. // A special case of Type 7. The p11, p12, p21, p22 automatically filled
  502. const coords = this.coords;
  503. const colors = this.colors;
  504. const ps = new Int32Array(16); // p00, p10, ..., p30, p01, ..., p33
  505. const cs = new Int32Array(4); // c00, c30, c03, c33
  506. while (reader.hasData) {
  507. const f = reader.readFlag();
  508. if (!(0 <= f && f <= 3)) {
  509. throw new FormatError("Unknown type6 flag");
  510. }
  511. const pi = coords.length;
  512. for (let i = 0, ii = f !== 0 ? 8 : 12; i < ii; i++) {
  513. coords.push(reader.readCoordinate());
  514. }
  515. const ci = colors.length;
  516. for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
  517. colors.push(reader.readComponents());
  518. }
  519. let tmp1, tmp2, tmp3, tmp4;
  520. switch (f) {
  521. // prettier-ignore
  522. case 0:
  523. ps[12] = pi + 3; ps[13] = pi + 4; ps[14] = pi + 5; ps[15] = pi + 6;
  524. ps[ 8] = pi + 2; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 7;
  525. ps[ 4] = pi + 1; /* calculated below */ ps[ 7] = pi + 8;
  526. ps[ 0] = pi; ps[ 1] = pi + 11; ps[ 2] = pi + 10; ps[ 3] = pi + 9;
  527. cs[2] = ci + 1; cs[3] = ci + 2;
  528. cs[0] = ci; cs[1] = ci + 3;
  529. break;
  530. // prettier-ignore
  531. case 1:
  532. tmp1 = ps[12]; tmp2 = ps[13]; tmp3 = ps[14]; tmp4 = ps[15];
  533. ps[12] = tmp4; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
  534. ps[ 8] = tmp3; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 3;
  535. ps[ 4] = tmp2; /* calculated below */ ps[ 7] = pi + 4;
  536. ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
  537. tmp1 = cs[2]; tmp2 = cs[3];
  538. cs[2] = tmp2; cs[3] = ci;
  539. cs[0] = tmp1; cs[1] = ci + 1;
  540. break;
  541. // prettier-ignore
  542. case 2:
  543. tmp1 = ps[15];
  544. tmp2 = ps[11];
  545. ps[12] = ps[3]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
  546. ps[ 8] = ps[7]; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 3;
  547. ps[ 4] = tmp2; /* calculated below */ ps[ 7] = pi + 4;
  548. ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
  549. tmp1 = cs[3];
  550. cs[2] = cs[1]; cs[3] = ci;
  551. cs[0] = tmp1; cs[1] = ci + 1;
  552. break;
  553. // prettier-ignore
  554. case 3:
  555. ps[12] = ps[0]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
  556. ps[ 8] = ps[1]; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 3;
  557. ps[ 4] = ps[2]; /* calculated below */ ps[ 7] = pi + 4;
  558. ps[ 0] = ps[3]; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
  559. cs[2] = cs[0]; cs[3] = ci;
  560. cs[0] = cs[1]; cs[1] = ci + 1;
  561. break;
  562. }
  563. // set p11, p12, p21, p22
  564. ps[5] = coords.length;
  565. coords.push([
  566. (-4 * coords[ps[0]][0] -
  567. coords[ps[15]][0] +
  568. 6 * (coords[ps[4]][0] + coords[ps[1]][0]) -
  569. 2 * (coords[ps[12]][0] + coords[ps[3]][0]) +
  570. 3 * (coords[ps[13]][0] + coords[ps[7]][0])) /
  571. 9,
  572. (-4 * coords[ps[0]][1] -
  573. coords[ps[15]][1] +
  574. 6 * (coords[ps[4]][1] + coords[ps[1]][1]) -
  575. 2 * (coords[ps[12]][1] + coords[ps[3]][1]) +
  576. 3 * (coords[ps[13]][1] + coords[ps[7]][1])) /
  577. 9,
  578. ]);
  579. ps[6] = coords.length;
  580. coords.push([
  581. (-4 * coords[ps[3]][0] -
  582. coords[ps[12]][0] +
  583. 6 * (coords[ps[2]][0] + coords[ps[7]][0]) -
  584. 2 * (coords[ps[0]][0] + coords[ps[15]][0]) +
  585. 3 * (coords[ps[4]][0] + coords[ps[14]][0])) /
  586. 9,
  587. (-4 * coords[ps[3]][1] -
  588. coords[ps[12]][1] +
  589. 6 * (coords[ps[2]][1] + coords[ps[7]][1]) -
  590. 2 * (coords[ps[0]][1] + coords[ps[15]][1]) +
  591. 3 * (coords[ps[4]][1] + coords[ps[14]][1])) /
  592. 9,
  593. ]);
  594. ps[9] = coords.length;
  595. coords.push([
  596. (-4 * coords[ps[12]][0] -
  597. coords[ps[3]][0] +
  598. 6 * (coords[ps[8]][0] + coords[ps[13]][0]) -
  599. 2 * (coords[ps[0]][0] + coords[ps[15]][0]) +
  600. 3 * (coords[ps[11]][0] + coords[ps[1]][0])) /
  601. 9,
  602. (-4 * coords[ps[12]][1] -
  603. coords[ps[3]][1] +
  604. 6 * (coords[ps[8]][1] + coords[ps[13]][1]) -
  605. 2 * (coords[ps[0]][1] + coords[ps[15]][1]) +
  606. 3 * (coords[ps[11]][1] + coords[ps[1]][1])) /
  607. 9,
  608. ]);
  609. ps[10] = coords.length;
  610. coords.push([
  611. (-4 * coords[ps[15]][0] -
  612. coords[ps[0]][0] +
  613. 6 * (coords[ps[11]][0] + coords[ps[14]][0]) -
  614. 2 * (coords[ps[12]][0] + coords[ps[3]][0]) +
  615. 3 * (coords[ps[2]][0] + coords[ps[8]][0])) /
  616. 9,
  617. (-4 * coords[ps[15]][1] -
  618. coords[ps[0]][1] +
  619. 6 * (coords[ps[11]][1] + coords[ps[14]][1]) -
  620. 2 * (coords[ps[12]][1] + coords[ps[3]][1]) +
  621. 3 * (coords[ps[2]][1] + coords[ps[8]][1])) /
  622. 9,
  623. ]);
  624. this.figures.push({
  625. type: "patch",
  626. coords: new Int32Array(ps), // making copies of ps and cs
  627. colors: new Int32Array(cs),
  628. });
  629. }
  630. }
  631. _decodeType7Shading(reader) {
  632. const coords = this.coords;
  633. const colors = this.colors;
  634. const ps = new Int32Array(16); // p00, p10, ..., p30, p01, ..., p33
  635. const cs = new Int32Array(4); // c00, c30, c03, c33
  636. while (reader.hasData) {
  637. const f = reader.readFlag();
  638. if (!(0 <= f && f <= 3)) {
  639. throw new FormatError("Unknown type7 flag");
  640. }
  641. const pi = coords.length;
  642. for (let i = 0, ii = f !== 0 ? 12 : 16; i < ii; i++) {
  643. coords.push(reader.readCoordinate());
  644. }
  645. const ci = colors.length;
  646. for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
  647. colors.push(reader.readComponents());
  648. }
  649. let tmp1, tmp2, tmp3, tmp4;
  650. switch (f) {
  651. // prettier-ignore
  652. case 0:
  653. ps[12] = pi + 3; ps[13] = pi + 4; ps[14] = pi + 5; ps[15] = pi + 6;
  654. ps[ 8] = pi + 2; ps[ 9] = pi + 13; ps[10] = pi + 14; ps[11] = pi + 7;
  655. ps[ 4] = pi + 1; ps[ 5] = pi + 12; ps[ 6] = pi + 15; ps[ 7] = pi + 8;
  656. ps[ 0] = pi; ps[ 1] = pi + 11; ps[ 2] = pi + 10; ps[ 3] = pi + 9;
  657. cs[2] = ci + 1; cs[3] = ci + 2;
  658. cs[0] = ci; cs[1] = ci + 3;
  659. break;
  660. // prettier-ignore
  661. case 1:
  662. tmp1 = ps[12]; tmp2 = ps[13]; tmp3 = ps[14]; tmp4 = ps[15];
  663. ps[12] = tmp4; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
  664. ps[ 8] = tmp3; ps[ 9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3;
  665. ps[ 4] = tmp2; ps[ 5] = pi + 8; ps[ 6] = pi + 11; ps[ 7] = pi + 4;
  666. ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
  667. tmp1 = cs[2]; tmp2 = cs[3];
  668. cs[2] = tmp2; cs[3] = ci;
  669. cs[0] = tmp1; cs[1] = ci + 1;
  670. break;
  671. // prettier-ignore
  672. case 2:
  673. tmp1 = ps[15];
  674. tmp2 = ps[11];
  675. ps[12] = ps[3]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
  676. ps[ 8] = ps[7]; ps[ 9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3;
  677. ps[ 4] = tmp2; ps[ 5] = pi + 8; ps[ 6] = pi + 11; ps[ 7] = pi + 4;
  678. ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
  679. tmp1 = cs[3];
  680. cs[2] = cs[1]; cs[3] = ci;
  681. cs[0] = tmp1; cs[1] = ci + 1;
  682. break;
  683. // prettier-ignore
  684. case 3:
  685. ps[12] = ps[0]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
  686. ps[ 8] = ps[1]; ps[ 9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3;
  687. ps[ 4] = ps[2]; ps[ 5] = pi + 8; ps[ 6] = pi + 11; ps[ 7] = pi + 4;
  688. ps[ 0] = ps[3]; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
  689. cs[2] = cs[0]; cs[3] = ci;
  690. cs[0] = cs[1]; cs[1] = ci + 1;
  691. break;
  692. }
  693. this.figures.push({
  694. type: "patch",
  695. coords: new Int32Array(ps), // making copies of ps and cs
  696. colors: new Int32Array(cs),
  697. });
  698. }
  699. }
  700. _buildFigureFromPatch(index) {
  701. const figure = this.figures[index];
  702. assert(figure.type === "patch", "Unexpected patch mesh figure");
  703. const coords = this.coords,
  704. colors = this.colors;
  705. const pi = figure.coords;
  706. const ci = figure.colors;
  707. const figureMinX = Math.min(
  708. coords[pi[0]][0],
  709. coords[pi[3]][0],
  710. coords[pi[12]][0],
  711. coords[pi[15]][0]
  712. );
  713. const figureMinY = Math.min(
  714. coords[pi[0]][1],
  715. coords[pi[3]][1],
  716. coords[pi[12]][1],
  717. coords[pi[15]][1]
  718. );
  719. const figureMaxX = Math.max(
  720. coords[pi[0]][0],
  721. coords[pi[3]][0],
  722. coords[pi[12]][0],
  723. coords[pi[15]][0]
  724. );
  725. const figureMaxY = Math.max(
  726. coords[pi[0]][1],
  727. coords[pi[3]][1],
  728. coords[pi[12]][1],
  729. coords[pi[15]][1]
  730. );
  731. let splitXBy = Math.ceil(
  732. ((figureMaxX - figureMinX) * MeshShading.TRIANGLE_DENSITY) /
  733. (this.bounds[2] - this.bounds[0])
  734. );
  735. splitXBy = Math.max(
  736. MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT,
  737. Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitXBy)
  738. );
  739. let splitYBy = Math.ceil(
  740. ((figureMaxY - figureMinY) * MeshShading.TRIANGLE_DENSITY) /
  741. (this.bounds[3] - this.bounds[1])
  742. );
  743. splitYBy = Math.max(
  744. MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT,
  745. Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitYBy)
  746. );
  747. const verticesPerRow = splitXBy + 1;
  748. const figureCoords = new Int32Array((splitYBy + 1) * verticesPerRow);
  749. const figureColors = new Int32Array((splitYBy + 1) * verticesPerRow);
  750. let k = 0;
  751. const cl = new Uint8Array(3),
  752. cr = new Uint8Array(3);
  753. const c0 = colors[ci[0]],
  754. c1 = colors[ci[1]],
  755. c2 = colors[ci[2]],
  756. c3 = colors[ci[3]];
  757. const bRow = getB(splitYBy),
  758. bCol = getB(splitXBy);
  759. for (let row = 0; row <= splitYBy; row++) {
  760. cl[0] = ((c0[0] * (splitYBy - row) + c2[0] * row) / splitYBy) | 0;
  761. cl[1] = ((c0[1] * (splitYBy - row) + c2[1] * row) / splitYBy) | 0;
  762. cl[2] = ((c0[2] * (splitYBy - row) + c2[2] * row) / splitYBy) | 0;
  763. cr[0] = ((c1[0] * (splitYBy - row) + c3[0] * row) / splitYBy) | 0;
  764. cr[1] = ((c1[1] * (splitYBy - row) + c3[1] * row) / splitYBy) | 0;
  765. cr[2] = ((c1[2] * (splitYBy - row) + c3[2] * row) / splitYBy) | 0;
  766. for (let col = 0; col <= splitXBy; col++, k++) {
  767. if (
  768. (row === 0 || row === splitYBy) &&
  769. (col === 0 || col === splitXBy)
  770. ) {
  771. continue;
  772. }
  773. let x = 0,
  774. y = 0;
  775. let q = 0;
  776. for (let i = 0; i <= 3; i++) {
  777. for (let j = 0; j <= 3; j++, q++) {
  778. const m = bRow[row][i] * bCol[col][j];
  779. x += coords[pi[q]][0] * m;
  780. y += coords[pi[q]][1] * m;
  781. }
  782. }
  783. figureCoords[k] = coords.length;
  784. coords.push([x, y]);
  785. figureColors[k] = colors.length;
  786. const newColor = new Uint8Array(3);
  787. newColor[0] = ((cl[0] * (splitXBy - col) + cr[0] * col) / splitXBy) | 0;
  788. newColor[1] = ((cl[1] * (splitXBy - col) + cr[1] * col) / splitXBy) | 0;
  789. newColor[2] = ((cl[2] * (splitXBy - col) + cr[2] * col) / splitXBy) | 0;
  790. colors.push(newColor);
  791. }
  792. }
  793. figureCoords[0] = pi[0];
  794. figureColors[0] = ci[0];
  795. figureCoords[splitXBy] = pi[3];
  796. figureColors[splitXBy] = ci[1];
  797. figureCoords[verticesPerRow * splitYBy] = pi[12];
  798. figureColors[verticesPerRow * splitYBy] = ci[2];
  799. figureCoords[verticesPerRow * splitYBy + splitXBy] = pi[15];
  800. figureColors[verticesPerRow * splitYBy + splitXBy] = ci[3];
  801. this.figures[index] = {
  802. type: "lattice",
  803. coords: figureCoords,
  804. colors: figureColors,
  805. verticesPerRow,
  806. };
  807. }
  808. _updateBounds() {
  809. let minX = this.coords[0][0],
  810. minY = this.coords[0][1],
  811. maxX = minX,
  812. maxY = minY;
  813. for (let i = 1, ii = this.coords.length; i < ii; i++) {
  814. const x = this.coords[i][0],
  815. y = this.coords[i][1];
  816. minX = minX > x ? x : minX;
  817. minY = minY > y ? y : minY;
  818. maxX = maxX < x ? x : maxX;
  819. maxY = maxY < y ? y : maxY;
  820. }
  821. this.bounds = [minX, minY, maxX, maxY];
  822. }
  823. _packData() {
  824. let i, ii, j, jj;
  825. const coords = this.coords;
  826. const coordsPacked = new Float32Array(coords.length * 2);
  827. for (i = 0, j = 0, ii = coords.length; i < ii; i++) {
  828. const xy = coords[i];
  829. coordsPacked[j++] = xy[0];
  830. coordsPacked[j++] = xy[1];
  831. }
  832. this.coords = coordsPacked;
  833. const colors = this.colors;
  834. const colorsPacked = new Uint8Array(colors.length * 3);
  835. for (i = 0, j = 0, ii = colors.length; i < ii; i++) {
  836. const c = colors[i];
  837. colorsPacked[j++] = c[0];
  838. colorsPacked[j++] = c[1];
  839. colorsPacked[j++] = c[2];
  840. }
  841. this.colors = colorsPacked;
  842. const figures = this.figures;
  843. for (i = 0, ii = figures.length; i < ii; i++) {
  844. const figure = figures[i],
  845. ps = figure.coords,
  846. cs = figure.colors;
  847. for (j = 0, jj = ps.length; j < jj; j++) {
  848. ps[j] *= 2;
  849. cs[j] *= 3;
  850. }
  851. }
  852. }
  853. getIR() {
  854. return [
  855. "Mesh",
  856. this.shadingType,
  857. this.coords,
  858. this.colors,
  859. this.figures,
  860. this.bounds,
  861. this.bbox,
  862. this.background,
  863. ];
  864. }
  865. }
  866. class DummyShading extends BaseShading {
  867. getIR() {
  868. return ["Dummy"];
  869. }
  870. }
  871. function getTilingPatternIR(operatorList, dict, color) {
  872. const matrix = dict.getArray("Matrix");
  873. const bbox = Util.normalizeRect(dict.getArray("BBox"));
  874. const xstep = dict.get("XStep");
  875. const ystep = dict.get("YStep");
  876. const paintType = dict.get("PaintType");
  877. const tilingType = dict.get("TilingType");
  878. // Ensure that the pattern has a non-zero width and height, to prevent errors
  879. // in `pattern_helper.js` (fixes issue8330.pdf).
  880. if (bbox[2] - bbox[0] === 0 || bbox[3] - bbox[1] === 0) {
  881. throw new FormatError(`Invalid getTilingPatternIR /BBox array: [${bbox}].`);
  882. }
  883. return [
  884. "TilingPattern",
  885. color,
  886. operatorList,
  887. matrix,
  888. bbox,
  889. xstep,
  890. ystep,
  891. paintType,
  892. tilingType,
  893. ];
  894. }
  895. export { getTilingPatternIR, Pattern };