From 5fa49ce3d6d9545a728ecc562c13df051c877308 Mon Sep 17 00:00:00 2001 From: sbarman Date: Thu, 16 Jun 2011 11:26:50 -0700 Subject: [PATCH 1/9] working version of tiling --- pdf.js | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index 17537d233..771b18c30 100644 --- a/pdf.js +++ b/pdf.js @@ -1590,8 +1590,38 @@ var CanvasExtraState = (function() { // Start of text line (in text coordinates) this.lineX = 0.0; this.lineY = 0.0; + + this.transMatrix = IDENTITY_MATRIX; } constructor.prototype = { + applyTransform: function(point) { + var m = this.transMatrix + var x = point[0] * m[0] + point[1] * m[2] + m[4]; + var y = point[0] * m[1] + point[1] * m[3] + m[5]; + return [x,y]; + }, + concatTransform: function(m) { + var tm = this.transMatrix; + + var a = m[0] * tm[0] + m[1] * tm[2]; + var b = m[0] * tm[1] + m[1] * tm[3]; + var c = m[2] * tm[0] + m[3] * tm[2]; + var d = m[2] * tm[1] + m[3] * tm[3]; + var e = m[4] * tm[0] + m[5] * tm[2] + tm[4]; + var f = m[4] * tm[1] + m[5] * tm[3] + tm[5]; + this.transMatrix = [a, b, c, d, e, f] + }, + getInvTransform: function(matrix) { + var m = this.transMatrix; + var det = 1 / (m[0] * m[3] - m[1] * m[2]); + var a = m[3] * det; + var b = -m[1] * det; + var c = -m[2] * det; + var d = m[0] * det; + var e = (m[2] * m[5] - m[3] * m[4]) * det; + var f = (m[1] * m[4] - m[0] * m[5]) * det; + return [a, b, c, d, e, f] + } }; return constructor; })(); @@ -1851,6 +1881,7 @@ var CanvasGraphics = (function() { }, transform: function(a, b, c, d, e, f) { this.ctx.transform(a, b, c, d, e, f); + this.current.concatTransform([a,b,c,d,e,f]); }, // Path @@ -2023,6 +2054,11 @@ var CanvasGraphics = (function() { }, setFillColorSpace: function(space) { // TODO real impl + if (space.name === "Pattern") { + this.colorspace = "Pattern"; + } else { + this.colorspace = null; + } }, setStrokeColor: function(/*...*/) { // TODO real impl @@ -2046,7 +2082,104 @@ var CanvasGraphics = (function() { }, setFillColorN: function(/*...*/) { // TODO real impl - this.setFillColor.apply(this, arguments); + var args = arguments; + if (this.colorspace == "Pattern") { + var patternName = args[0]; + if (IsName(patternName)) { + var xref = this.xref; + var patternRes = xref.fetchIfRef(this.res.get("Pattern")); + if (!patternRes) + error("Unable to find pattern resource"); + + var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); + + var type = pattern.dict.get("PatternType"); + if (type === 1) { + this.tilingFill(pattern); + } else { + error("Unhandled pattern type"); + } + } + } else { + // TODO real impl + this.setFillColor.apply(this, arguments); + } + }, + tilingFill: function(pattern) { + this.save(); + var dict = pattern.dict; + + var paintType = dict.get("PaintType"); + if (paintType == 2) { + error("Unsupported paint type"); + } else { + // should go to default for color space + this.ctx.fillStyle = this.makeCssRgb(1, 1, 1); + this.ctx.strokeStyle = this.makeCssRgb(0, 0, 0); + } + + // not sure what to do with this + var tilingType = dict.get("TilingType"); + + var tempExtra = new CanvasExtraState(); + var matrix = dict.get("Matrix"); + if (matrix && IsArray(matrix) && 6 == matrix.length) + tempExtra.transMatrix = matrix; + + var bbox = dict.get("BBox"); + var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; + + var xstep = dict.get("XStep"); + var ystep = dict.get("YStep"); + + // top left corner should correspond to the top left of the bbox + var topLeft = tempExtra.applyTransform([x0,y0]); + // we want the canvas to be as large as the step size + var botRight = tempExtra.applyTransform([x0 + xstep, y0 + ystep]); + + var tmpCanvas = document.createElement("canvas"); + tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]); + tmpCanvas.height = Math.ceil(botRight[1] - topLeft[1]); + + // set the new canvas element context as the graphics context + var tmpCtx = tmpCanvas.getContext("2d"); + var oldCtx = this.ctx; + this.ctx = tmpCtx; + + // normalize matrix transform so each step + // takes up the entire tmpCanvas + if (matrix[1] === 0 && matrix[2] === 0) { + matrix[0] = tmpCanvas.width / xstep; + matrix[3] = tmpCanvas.height / ystep; + tempExtra.transMatrix = matrix; + topLeft = tempExtra.applyTransform([x0,y0]); + } + + // move the top left corner of bounding box to [0,0] + tempExtra.transMatrix = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; + tempExtra.concatTransform(matrix); + matrix = tempExtra.transMatrix; + + this.transform.apply(this, matrix); + + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } + + var xref = this.xref; + var res = xref.fetchIfRef(dict.get("Resources")); + if (!pattern.code) + pattern.code = this.compile(pattern, xref, res, []); + this.execute(pattern.code, xref, res); + + // set the old context + this.ctx = oldCtx; + this.restore(); + + var pattern = this.ctx.createPattern(tmpCanvas, "repeat"); + this.ctx.fillStyle = pattern; }, setStrokeGray: function(gray) { this.setStrokeRGBColor(gray, gray, gray); From 72919470a993ea68e427e80a619235f136aae668 Mon Sep 17 00:00:00 2001 From: sbarman Date: Thu, 16 Jun 2011 12:03:50 -0700 Subject: [PATCH 2/9] Added comments --- pdf.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pdf.js b/pdf.js index 17537d233..904ff3906 100644 --- a/pdf.js +++ b/pdf.js @@ -2134,6 +2134,10 @@ var CanvasGraphics = (function() { fn = new PDFFunction(this.xref, fnObj); var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); + + // 10 samples seems good enough for now, but probably won't work + // if there are sharp color changes. Ideally, we could see the + // current image size and base the # samples on that. var step = (t1 - t0) / 10; for (var i = t0; i <= t1; i += step) { @@ -2146,6 +2150,8 @@ var CanvasGraphics = (function() { // HACK to draw the gradient onto an infinite rectangle. // PDF gradients are drawn across the entire image while // Canvas only allows gradients to be drawn in a rectangle + // Also, larger numbers seem to cause overflow which causes + // nothing to be drawn. this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); }, From 518c7d3328ff1304fe4319c5b291a5f3cca1c84b Mon Sep 17 00:00:00 2001 From: sbarman Date: Fri, 17 Jun 2011 17:35:56 -0700 Subject: [PATCH 3/9] Cleaned up code for tiling --- pdf.js | 70 ++++++++++++++++++++++------------------------------------ 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/pdf.js b/pdf.js index 85c9489ae..0d18b272f 100644 --- a/pdf.js +++ b/pdf.js @@ -1609,38 +1609,8 @@ var CanvasExtraState = (function() { // Start of text line (in text coordinates) this.lineX = 0.0; this.lineY = 0.0; - - this.transMatrix = IDENTITY_MATRIX; } constructor.prototype = { - applyTransform: function(point) { - var m = this.transMatrix - var x = point[0] * m[0] + point[1] * m[2] + m[4]; - var y = point[0] * m[1] + point[1] * m[3] + m[5]; - return [x,y]; - }, - concatTransform: function(m) { - var tm = this.transMatrix; - - var a = m[0] * tm[0] + m[1] * tm[2]; - var b = m[0] * tm[1] + m[1] * tm[3]; - var c = m[2] * tm[0] + m[3] * tm[2]; - var d = m[2] * tm[1] + m[3] * tm[3]; - var e = m[4] * tm[0] + m[5] * tm[2] + tm[4]; - var f = m[4] * tm[1] + m[5] * tm[3] + tm[5]; - this.transMatrix = [a, b, c, d, e, f] - }, - getInvTransform: function(matrix) { - var m = this.transMatrix; - var det = 1 / (m[0] * m[3] - m[1] * m[2]); - var a = m[3] * det; - var b = -m[1] * det; - var c = -m[2] * det; - var d = m[0] * det; - var e = (m[2] * m[5] - m[3] * m[4]) * det; - var f = (m[1] * m[4] - m[0] * m[5]) * det; - return [a, b, c, d, e, f] - } }; return constructor; })(); @@ -1942,7 +1912,6 @@ var CanvasGraphics = (function() { }, transform: function(a, b, c, d, e, f) { this.ctx.transform(a, b, c, d, e, f); - this.current.concatTransform([a,b,c,d,e,f]); }, // Path @@ -2181,6 +2150,22 @@ var CanvasGraphics = (function() { } }, tilingFill: function(pattern) { + function applyMatrix(point, m) { + var x = point[0] * m[0] + point[1] * m[2] + m[4]; + var y = point[0] * m[1] + point[1] * m[3] + m[5]; + return [x,y]; + }; + + function multiply(m, tm) { + var a = m[0] * tm[0] + m[1] * tm[2]; + var b = m[0] * tm[1] + m[1] * tm[3]; + var c = m[2] * tm[0] + m[3] * tm[2]; + var d = m[2] * tm[1] + m[3] * tm[3]; + var e = m[4] * tm[0] + m[5] * tm[2] + tm[4]; + var f = m[4] * tm[1] + m[5] * tm[3] + tm[5]; + return [a, b, c, d, e, f] + }; + this.save(); var dict = pattern.dict; @@ -2196,10 +2181,9 @@ var CanvasGraphics = (function() { // not sure what to do with this var tilingType = dict.get("TilingType"); - var tempExtra = new CanvasExtraState(); var matrix = dict.get("Matrix"); - if (matrix && IsArray(matrix) && 6 == matrix.length) - tempExtra.transMatrix = matrix; + if (!matrix) + matrix = [1, 0, 0, 1, 0, 0]; var bbox = dict.get("BBox"); var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; @@ -2208,9 +2192,9 @@ var CanvasGraphics = (function() { var ystep = dict.get("YStep"); // top left corner should correspond to the top left of the bbox - var topLeft = tempExtra.applyTransform([x0,y0]); + var topLeft = applyMatrix([x0,y0], matrix); // we want the canvas to be as large as the step size - var botRight = tempExtra.applyTransform([x0 + xstep, y0 + ystep]); + var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix); var tmpCanvas = document.createElement("canvas"); tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]); @@ -2221,20 +2205,17 @@ var CanvasGraphics = (function() { var oldCtx = this.ctx; this.ctx = tmpCtx; - // normalize matrix transform so each step - // takes up the entire tmpCanvas + // normalize transform matrix so each step + // takes up the entire tmpCanvas (no white borders) if (matrix[1] === 0 && matrix[2] === 0) { matrix[0] = tmpCanvas.width / xstep; matrix[3] = tmpCanvas.height / ystep; - tempExtra.transMatrix = matrix; - topLeft = tempExtra.applyTransform([x0,y0]); + topLeft = applyMatrix([x0,y0], matrix); } // move the top left corner of bounding box to [0,0] - tempExtra.transMatrix = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; - tempExtra.concatTransform(matrix); - matrix = tempExtra.transMatrix; - + matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]); + this.transform.apply(this, matrix); if (bbox && IsArray(bbox) && 4 == bbox.length) { @@ -2253,6 +2234,7 @@ var CanvasGraphics = (function() { this.ctx = oldCtx; this.restore(); + warn("Inverse pattern is painted"); var pattern = this.ctx.createPattern(tmpCanvas, "repeat"); this.ctx.fillStyle = pattern; }, From 698132daec46bc8a2a66b0c07d84b2663662fcb6 Mon Sep 17 00:00:00 2001 From: sbarman Date: Fri, 17 Jun 2011 17:46:02 -0700 Subject: [PATCH 4/9] clean up tiling --- pdf.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pdf.js b/pdf.js index 2fc51a21c..9c8b7e6a0 100644 --- a/pdf.js +++ b/pdf.js @@ -2110,11 +2110,10 @@ var CanvasGraphics = (function() { }, setFillColorSpace: function(space) { // TODO real impl - if (space.name === "Pattern") { + if (space.name === "Pattern") this.colorspace = "Pattern"; - } else { + else this.colorspace = null; - } }, setStrokeColor: function(/*...*/) { // TODO real impl @@ -2150,11 +2149,10 @@ var CanvasGraphics = (function() { var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); var type = pattern.dict.get("PatternType"); - if (type === 1) { + if (type === 1) this.tilingFill(pattern); - } else { + else error("Unhandled pattern type"); - } } } else { // TODO real impl @@ -2218,7 +2216,7 @@ var CanvasGraphics = (function() { this.ctx = tmpCtx; // normalize transform matrix so each step - // takes up the entire tmpCanvas (no white borders) + // takes up the entire tmpCanvas (need to remove white borders) if (matrix[1] === 0 && matrix[2] === 0) { matrix[0] = tmpCanvas.width / xstep; matrix[3] = tmpCanvas.height / ystep; From e80a8e6e2bdffca1ee51a40e4fde932163917751 Mon Sep 17 00:00:00 2001 From: sbarman Date: Fri, 17 Jun 2011 17:48:44 -0700 Subject: [PATCH 5/9] clean up tiling --- pdf.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index 9c8b7e6a0..20a91d9af 100644 --- a/pdf.js +++ b/pdf.js @@ -2184,8 +2184,9 @@ var CanvasGraphics = (function() { error("Unsupported paint type"); } else { // should go to default for color space - this.ctx.fillStyle = this.makeCssRgb(1, 1, 1); - this.ctx.strokeStyle = this.makeCssRgb(0, 0, 0); + var ctx = this.ctx; + ctx.fillStyle = this.makeCssRgb(1, 1, 1); + ctx.strokeStyle = this.makeCssRgb(0, 0, 0); } // not sure what to do with this From e9a9de8a1b4ca6eb68faf79aee0aaabc2a026726 Mon Sep 17 00:00:00 2001 From: sbarman Date: Sun, 19 Jun 2011 15:01:52 -0500 Subject: [PATCH 6/9] cleanup --- pdf.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pdf.js b/pdf.js index 233773cb4..bf8ff1d97 100644 --- a/pdf.js +++ b/pdf.js @@ -1738,6 +1738,7 @@ var CanvasExtraState = (function() { this.fontSize = 0.0; this.textMatrix = IDENTITY_MATRIX; this.leading = 0.0; + this.colorSpace = "DeviceRGB"; // Current point (in user coordinates) this.x = 0.0; this.y = 0.0; @@ -2424,9 +2425,9 @@ var CanvasGraphics = (function() { setFillColorSpace: function(space) { // TODO real impl if (space.name === "Pattern") - this.colorspace = "Pattern"; + this.current.colorSpace = "Pattern"; else - this.colorspace = null; + this.current.colorSpace = "DeviceRGB"; }, setStrokeColor: function(/*...*/) { // TODO real impl @@ -2451,7 +2452,7 @@ var CanvasGraphics = (function() { setFillColorN: function(/*...*/) { // TODO real impl var args = arguments; - if (this.colorspace == "Pattern") { + if (this.current.colorSpace == "Pattern") { var patternName = args[0]; if (IsName(patternName)) { var xref = this.xref; @@ -2502,12 +2503,9 @@ var CanvasGraphics = (function() { ctx.strokeStyle = this.makeCssRgb(0, 0, 0); } - // not sure what to do with this - var tilingType = dict.get("TilingType"); + TODO("TilingType"); - var matrix = dict.get("Matrix"); - if (!matrix) - matrix = [1, 0, 0, 1, 0, 0]; + var matrix = dict.get("Matrix") || IDENTITY_MATRIX; var bbox = dict.get("BBox"); var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; @@ -2526,7 +2524,7 @@ var CanvasGraphics = (function() { // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext("2d"); - var oldCtx = this.ctx; + var savedCtx = this.ctx; this.ctx = tmpCtx; // normalize transform matrix so each step @@ -2554,11 +2552,10 @@ var CanvasGraphics = (function() { pattern.code = this.compile(pattern, xref, res, []); this.execute(pattern.code, xref, res); - // set the old context - this.ctx = oldCtx; + this.ctx = savedCtx; this.restore(); - warn("Inverse pattern is painted"); + TODO("Inverse pattern is painted"); var pattern = this.ctx.createPattern(tmpCanvas, "repeat"); this.ctx.fillStyle = pattern; }, @@ -2650,8 +2647,8 @@ var CanvasGraphics = (function() { var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); // 10 samples seems good enough for now, but probably won't work - // if there are sharp color changes. Ideally, we could see the - // current image size and base the # samples on that. + // if there are sharp color changes. Ideally, we would implement + // the spec faithfully and add lossless optimizations. var step = (t1 - t0) / 10; for (var i = t0; i <= t1; i += step) { @@ -2664,7 +2661,10 @@ var CanvasGraphics = (function() { // HACK to draw the gradient onto an infinite rectangle. // PDF gradients are drawn across the entire image while // Canvas only allows gradients to be drawn in a rectangle - // Also, larger numbers seem to cause overflow which causes + // The following bug should allow us to remove this. + // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 + // + // Also, larg numbers seem to cause overflow which causes // nothing to be drawn. this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); }, From 0b2d842f830a4b2fb364590aae4c618d86defc47 Mon Sep 17 00:00:00 2001 From: sbarman Date: Mon, 20 Jun 2011 13:38:27 -0700 Subject: [PATCH 7/9] Used symbolic constants --- pdf.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pdf.js b/pdf.js index bf8ff1d97..15b63c1ba 100644 --- a/pdf.js +++ b/pdf.js @@ -2033,6 +2033,9 @@ var CanvasGraphics = (function() { const NORMAL_CLIP = {}; const EO_CLIP = {}; + // Used for tiling patterns + const PAINT_TYPE = [null, "colored", "uncolored"]; + constructor.prototype = { translateFont: function(fontDict, xref, resources) { var descriptor = xref.fetch(fontDict.get("FontDescriptor")); @@ -2461,12 +2464,13 @@ var CanvasGraphics = (function() { error("Unable to find pattern resource"); var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); - - var type = pattern.dict.get("PatternType"); - if (type === 1) - this.tilingFill(pattern); - else + + const types = [null, this.tilingFill]; + var typeNum = pattern.dict.get("PatternType"); + var patternFn = types[typeNum]; + if (!patternFn) error("Unhandled pattern type"); + patternFn.call(this, pattern); } } else { // TODO real impl @@ -2492,15 +2496,15 @@ var CanvasGraphics = (function() { this.save(); var dict = pattern.dict; + var ctx = this.ctx; - var paintType = dict.get("PaintType"); - if (paintType == 2) { - error("Unsupported paint type"); - } else { + var paintType = PAINT_TYPE[dict.get("PaintType")]; + if (paintType == "colored") { // should go to default for color space - var ctx = this.ctx; ctx.fillStyle = this.makeCssRgb(1, 1, 1); ctx.strokeStyle = this.makeCssRgb(0, 0, 0); + } else { + error("Unsupported paint type"); } TODO("TilingType"); @@ -2524,7 +2528,7 @@ var CanvasGraphics = (function() { // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext("2d"); - var savedCtx = this.ctx; + var savedCtx = ctx; this.ctx = tmpCtx; // normalize transform matrix so each step @@ -2663,9 +2667,6 @@ var CanvasGraphics = (function() { // Canvas only allows gradients to be drawn in a rectangle // The following bug should allow us to remove this. // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 - // - // Also, larg numbers seem to cause overflow which causes - // nothing to be drawn. this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); }, From 8ae9b1f3d4f9a2a9bd677e8665ba0122a8888fb4 Mon Sep 17 00:00:00 2001 From: sbarman Date: Mon, 20 Jun 2011 13:47:42 -0700 Subject: [PATCH 8/9] fixed ExtraStateContext.colorSpace impl --- pdf.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pdf.js b/pdf.js index 15b63c1ba..001784eea 100644 --- a/pdf.js +++ b/pdf.js @@ -1738,7 +1738,7 @@ var CanvasExtraState = (function() { this.fontSize = 0.0; this.textMatrix = IDENTITY_MATRIX; this.leading = 0.0; - this.colorSpace = "DeviceRGB"; + this.colorSpace = null; // Current point (in user coordinates) this.x = 0.0; this.y = 0.0; @@ -2454,9 +2454,17 @@ var CanvasGraphics = (function() { }, setFillColorN: function(/*...*/) { // TODO real impl - var args = arguments; + var colorSpace = this.current.colorSpace; + if (!colorSpace) { + var stateStack = this.stateStack; + var i = stateStack.length - 1; + while (!colorSpace && i >= 0) { + colorSpace = stateStack[i--].colorSpace; + } + } + if (this.current.colorSpace == "Pattern") { - var patternName = args[0]; + var patternName = arguments[0]; if (IsName(patternName)) { var xref = this.xref; var patternRes = xref.fetchIfRef(this.res.get("Pattern")); From 8911f7c6c5241c152debceabd3e47d1ac43fdaf0 Mon Sep 17 00:00:00 2001 From: sbarman Date: Mon, 20 Jun 2011 14:22:11 -0700 Subject: [PATCH 9/9] switched to using const enums --- pdf.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pdf.js b/pdf.js index 001784eea..aca7e0b5a 100644 --- a/pdf.js +++ b/pdf.js @@ -2034,7 +2034,7 @@ var CanvasGraphics = (function() { const EO_CLIP = {}; // Used for tiling patterns - const PAINT_TYPE = [null, "colored", "uncolored"]; + const PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; constructor.prototype = { translateFont: function(fontDict, xref, resources) { @@ -2506,12 +2506,15 @@ var CanvasGraphics = (function() { var dict = pattern.dict; var ctx = this.ctx; - var paintType = PAINT_TYPE[dict.get("PaintType")]; - if (paintType == "colored") { + var paintType = dict.get("PaintType"); + switch (paintType) { + case PAINT_TYPE_COLORED: // should go to default for color space ctx.fillStyle = this.makeCssRgb(1, 1, 1); ctx.strokeStyle = this.makeCssRgb(0, 0, 0); - } else { + break; + case PAINT_TYPE_UNCOLORED: + default: error("Unsupported paint type"); }