mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-29 06:57:03 +00:00
As part of the ongoing effort to remove the C-preprocessor (CPP) from the new Lisette compiler pipeline, this commit refactors CGContextCanvas.j to use native JavaScript and HTML5 Canvas APIs directly. Key changes: - Removed all `#define` macros that wrapped standard Canvas API calls (e.g., `_CGContextMoveToPointCanvas`). Direct method invocations are now used, eliminating the preprocessor pass and simplifying AST generation. - Removed the `CPJavaScriptCanvasTransformFeature` runtime check and its complex mathematical fallbacks (`scale_rotate`, `rotate_scale`, `eigen`). Modern target environments universally support native canvas transformations. - Removed obsolete vendor-prefixed line dash properties (`webkitLineDash`, `mozDash`) in favor of the standard `setLineDash`. - Replaced `var` with `const`/`let` for proper block scoping and immutability. - Replaced the `ROUND` macro with standard `Math.round`. These changes strip away 2008-era browser compatibility hacks and micro-optimizations, resulting in cleaner, more maintainable, and strictly browser-targeted code.
539 lines
14 KiB
Plaintext
539 lines
14 KiB
Plaintext
/*
|
|
* CGContextCanvas.j
|
|
* AppKit
|
|
*
|
|
* Created by Francisco Tolmasky.
|
|
* Modified by David Richardson.
|
|
* Copyright 2008, 280 North, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
// MODIFICATION: Converted 'var' to 'const' for immutable lookup tables.
|
|
const CANVAS_LINECAP_TABLE = [ "butt", "round", "square" ];
|
|
const CANVAS_LINEJOIN_TABLE = [ "miter", "round", "bevel" ];
|
|
const CANVAS_COMPOSITE_TABLE = [ "source-over", "source-over", "source-over", "source-over", "darker",
|
|
"lighter", "source-over", "source-over", "source-over", "source-over",
|
|
"source-over", "source-over", "source-over", "source-over", "source-over",
|
|
"source-over", "source-over",
|
|
"copy", "source-in", "source-out", "source-atop",
|
|
"destination-over", "destination-in", "destination-out", "destination-atop",
|
|
"xor", "source-over", "source-over" ];
|
|
|
|
// MODIFICATION: Removed all #define macros. Canvas methods are now invoked directly within the target functions
|
|
// to eliminate the preprocessor pass and simplify AST generation.
|
|
|
|
// In Cocoa, all primitives excepts rects and arcs cannot be added to the context's path
|
|
// until a move to point has been done, because an empty path has no current point.
|
|
// MODIFICATION: Converted to const function expression and applied Allman formatting.
|
|
const hasPath = function(aContext, methodName)
|
|
{
|
|
if (!aContext.hasPath)
|
|
{
|
|
CPLog.error(methodName + ": no current point");
|
|
}
|
|
|
|
return aContext.hasPath;
|
|
};
|
|
|
|
function CGContextSaveGState(aContext)
|
|
{
|
|
aContext.save();
|
|
}
|
|
|
|
function CGContextRestoreGState(aContext)
|
|
{
|
|
aContext.restore();
|
|
}
|
|
|
|
function CGContextSetLineCap(aContext, aLineCap)
|
|
{
|
|
aContext.lineCap = CANVAS_LINECAP_TABLE[aLineCap];
|
|
}
|
|
|
|
function CGContextSetLineDash(aContext, aPhase, someDashes)
|
|
{
|
|
// MODIFICATION: Removed obsolete vendor prefixes (webkitLineDash, mozDash).
|
|
if (aContext.setLineDash)
|
|
{
|
|
aContext.setLineDash(someDashes);
|
|
aContext.lineDashOffset = aPhase;
|
|
}
|
|
else if (someDashes)
|
|
{
|
|
CPLog.warn("CGContextSetLineDash not implemented in this environment.");
|
|
}
|
|
}
|
|
|
|
function CGContextSetLineJoin(aContext, aLineJoin)
|
|
{
|
|
aContext.lineJoin = CANVAS_LINEJOIN_TABLE[aLineJoin];
|
|
}
|
|
|
|
function CGContextSetLineWidth(aContext, aLineWidth)
|
|
{
|
|
aContext.lineWidth = aLineWidth;
|
|
}
|
|
|
|
function CGContextSetMiterLimit(aContext, aMiterLimit)
|
|
{
|
|
aContext.miterLimit = aMiterLimit;
|
|
}
|
|
|
|
function CGContextSetBlendMode(aContext, aBlendMode)
|
|
{
|
|
aContext.globalCompositeOperation = CANVAS_COMPOSITE_TABLE[aBlendMode];
|
|
}
|
|
|
|
function CGContextAddArc(aContext, x, y, radius, startAngle, endAngle, clockwise)
|
|
{
|
|
// Despite the documentation saying otherwise, the last parameter is anti-clockwise not clockwise.
|
|
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes#Arcs
|
|
// MODIFICATION: Direct invocation replacing _CGContextAddArcCanvas macro.
|
|
aContext.arc(x, y, radius, startAngle, endAngle, !clockwise);
|
|
|
|
// AddArc implicitly starts a path
|
|
aContext.hasPath = YES;
|
|
}
|
|
|
|
function CGContextAddArcToPoint(aContext, x1, y1, x2, y2, radius)
|
|
{
|
|
if (!hasPath(aContext, "CGContextAddArcToPoint"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// MODIFICATION: Direct invocation replacing _CGContextAddArcToPointCanvas macro.
|
|
aContext.arcTo(x1, y1, x2, y2, radius);
|
|
}
|
|
|
|
function CGContextAddCurveToPoint(aContext, cp1x, cp1y, cp2x, cp2y, x, y)
|
|
{
|
|
if (!hasPath(aContext, "CGContextAddCurveToPoint"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// MODIFICATION: Direct invocation replacing _CGContextAddCurveToPointCanvas macro.
|
|
aContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
|
}
|
|
|
|
function CGContextAddLines(aContext, points, count)
|
|
{
|
|
// implementation mirrors that of CGPathAddLines()
|
|
if (count == null)
|
|
{
|
|
count = points.length;
|
|
}
|
|
|
|
if (count < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// MODIFICATION: Direct invocations replacing macros.
|
|
aContext.moveTo(points[0].x, points[0].y);
|
|
|
|
// MODIFICATION: Replaced 'var' with 'let'.
|
|
for (let i = 1; i < count; ++i)
|
|
{
|
|
aContext.lineTo(points[i].x, points[i].y);
|
|
}
|
|
|
|
aContext.hasPath = YES;
|
|
}
|
|
|
|
function CGContextAddLineToPoint(aContext, x, y)
|
|
{
|
|
if (!hasPath(aContext, "CGContextAddLineToPoint"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// MODIFICATION: Direct invocation replacing macro.
|
|
aContext.lineTo(x, y);
|
|
}
|
|
|
|
function CGContextAddPath(aContext, aPath)
|
|
{
|
|
if (!aContext || CGPathIsEmpty(aPath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the context does not have a path, explicitly begin one
|
|
if (!aContext.hasPath)
|
|
{
|
|
aContext.beginPath();
|
|
}
|
|
|
|
// We must implicitly move to the start of the path
|
|
aContext.moveTo(aPath.start.x, aPath.start.y);
|
|
|
|
// MODIFICATION: Replaced 'var' with 'const'/'let'.
|
|
const elements = aPath.elements,
|
|
count = aPath.count;
|
|
|
|
for (let i = 0; i < count; ++i)
|
|
{
|
|
const element = elements[i],
|
|
type = element.type;
|
|
|
|
switch (type)
|
|
{
|
|
case kCGPathElementMoveToPoint:
|
|
aContext.moveTo(element.x, element.y);
|
|
break;
|
|
|
|
case kCGPathElementAddLineToPoint:
|
|
aContext.lineTo(element.x, element.y);
|
|
break;
|
|
|
|
case kCGPathElementAddQuadCurveToPoint:
|
|
aContext.quadraticCurveTo(element.cpx, element.cpy, element.x, element.y);
|
|
break;
|
|
|
|
case kCGPathElementAddCurveToPoint:
|
|
aContext.bezierCurveTo(element.cp1x, element.cp1y, element.cp2x, element.cp2y, element.x, element.y);
|
|
break;
|
|
|
|
case kCGPathElementCloseSubpath:
|
|
aContext.closePath();
|
|
break;
|
|
|
|
case kCGPathElementAddArc:
|
|
aContext.arc(element.x, element.y, element.radius, element.startAngle, element.endAngle, element.clockwise);
|
|
break;
|
|
|
|
case kCGPathElementAddArcToPoint:
|
|
aContext.arcTo(element.p1x, element.p1y, element.p2x, element.p2y, element.radius);
|
|
break;
|
|
}
|
|
}
|
|
|
|
aContext.hasPath = YES;
|
|
}
|
|
|
|
function CGContextAddRect(aContext, aRect)
|
|
{
|
|
// MODIFICATION: Direct invocation replacing _CGContextAddRectCanvas macro.
|
|
aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
|
|
aContext.hasPath = YES;
|
|
}
|
|
|
|
function CGContextAddQuadCurveToPoint(aContext, cpx, cpy, x, y)
|
|
{
|
|
if (!hasPath(aContext, "CGContextAddQuadCurveToPoint"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// MODIFICATION: Direct invocation replacing macro.
|
|
aContext.quadraticCurveTo(cpx, cpy, x, y);
|
|
}
|
|
|
|
function CGContextAddRects(aContext, rects, count)
|
|
{
|
|
if (count == null)
|
|
{
|
|
count = rects.length;
|
|
}
|
|
|
|
for (let i = 0; i < count; ++i)
|
|
{
|
|
const rect = rects[i];
|
|
aContext.rect(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect));
|
|
}
|
|
|
|
aContext.hasPath = YES;
|
|
}
|
|
|
|
function CGContextBeginPath(aContext)
|
|
{
|
|
aContext.beginPath();
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextClosePath(aContext)
|
|
{
|
|
aContext.closePath();
|
|
}
|
|
|
|
function CGContextIsPathEmpty(aContext)
|
|
{
|
|
return !aContext.hasPath;
|
|
}
|
|
|
|
function CGContextMoveToPoint(aContext, x, y)
|
|
{
|
|
aContext.moveTo(x, y);
|
|
aContext.hasPath = YES;
|
|
}
|
|
|
|
function CGContextClearRect(aContext, aRect)
|
|
{
|
|
aContext.clearRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextDrawPath(aContext, aMode)
|
|
{
|
|
if (!aContext.hasPath)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (aMode === kCGPathFill || aMode === kCGPathFillStroke)
|
|
{
|
|
aContext.fill();
|
|
}
|
|
else if (aMode === kCGPathStroke || aMode === kCGPathFillStroke || aMode === kCGPathEOFillStroke)
|
|
{
|
|
aContext.stroke();
|
|
}
|
|
else if (aMode === kCGPathEOFill || aMode === kCGPathEOFillStroke)
|
|
{
|
|
CPLog.warn("Unimplemented fill mode in CGContextDrawPath: %d", aMode);
|
|
}
|
|
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextFillRect(aContext, aRect)
|
|
{
|
|
// MODIFICATION: Direct invocation replacing macro.
|
|
aContext.fillRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextFillRects(aContext, rects, count)
|
|
{
|
|
if (count == null)
|
|
{
|
|
count = rects.length;
|
|
}
|
|
|
|
for (let i = 0; i < count; ++i)
|
|
{
|
|
const rect = rects[i];
|
|
aContext.fillRect(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect));
|
|
}
|
|
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextStrokeRect(aContext, aRect)
|
|
{
|
|
aContext.strokeRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextClip(aContext)
|
|
{
|
|
aContext.clip();
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextClipToRect(aContext, aRect)
|
|
{
|
|
aContext.beginPath();
|
|
aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
|
|
aContext.closePath();
|
|
|
|
aContext.clip();
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextClipToRects(aContext, rects, count)
|
|
{
|
|
if (count == null)
|
|
{
|
|
count = rects.length;
|
|
}
|
|
|
|
aContext.beginPath();
|
|
CGContextAddRects(aContext, rects, count);
|
|
aContext.clip();
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextSetAlpha(aContext, anAlpha)
|
|
{
|
|
aContext.globalAlpha = anAlpha;
|
|
}
|
|
|
|
function CGContextSetFillColor(aContext, aColor)
|
|
{
|
|
const patternImage = [aColor patternImage];
|
|
|
|
if ([patternImage isSingleImage])
|
|
{
|
|
const pattern = aContext.createPattern([patternImage image], "repeat");
|
|
aContext.fillStyle = pattern;
|
|
}
|
|
else
|
|
{
|
|
aContext.fillStyle = [aColor cssString];
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Creates a context into which you can render a fill pattern
|
|
of the given size. Once the pattern is rendered, you can
|
|
set the fill or stroke pattern to the rendered pattern
|
|
with CGContextSetFillPattern or CGContextSetStrokePattern.
|
|
*/
|
|
function CGContextCreatePatternContext(aContext, aSize)
|
|
{
|
|
const pattern = document.createElement("canvas");
|
|
|
|
pattern.width = aSize.width;
|
|
pattern.height = aSize.height;
|
|
|
|
return pattern.getContext("2d");
|
|
}
|
|
|
|
/*!
|
|
Sets the fill pattern for aContext to the rendered pattern context
|
|
returned by CGContextCreatePatternContext.
|
|
*/
|
|
function CGContextSetFillPattern(aContext, aPatternContext)
|
|
{
|
|
const pattern = aContext.createPattern(aPatternContext.canvas, "repeat");
|
|
aContext.fillStyle = pattern;
|
|
}
|
|
|
|
/*!
|
|
Sets the stroke pattern for aContext to the rendered pattern context
|
|
returned by CGContextCreatePatternContext.
|
|
*/
|
|
function CGContextSetStrokePattern(aContext, aPatternContext)
|
|
{
|
|
const pattern = aContext.createPattern(aPatternContext.canvas, "repeat");
|
|
aContext.strokeStyle = pattern;
|
|
}
|
|
|
|
function CGContextSetStrokeColor(aContext, aColor)
|
|
{
|
|
const patternImage = [aColor patternImage];
|
|
|
|
if ([patternImage isSingleImage])
|
|
{
|
|
const pattern = aContext.createPattern([patternImage image], "repeat");
|
|
aContext.strokeStyle = pattern;
|
|
}
|
|
else
|
|
{
|
|
aContext.strokeStyle = [aColor cssString];
|
|
}
|
|
}
|
|
|
|
function CGContextSetShadow(aContext, aSize, aBlur)
|
|
{
|
|
aContext.shadowOffsetX = aSize.width;
|
|
aContext.shadowOffsetY = aSize.height;
|
|
aContext.shadowBlur = aBlur;
|
|
aContext.shadowColor = [[CPColor shadowColor] cssString];
|
|
}
|
|
|
|
function CGContextSetShadowWithColor(aContext, aSize, aBlur, aColor)
|
|
{
|
|
aContext.shadowOffsetX = aSize.width;
|
|
aContext.shadowOffsetY = aSize.height;
|
|
aContext.shadowBlur = aBlur;
|
|
aContext.shadowColor = [aColor cssString];
|
|
}
|
|
|
|
function CGContextRotateCTM(aContext, anAngle)
|
|
{
|
|
aContext.rotate(anAngle);
|
|
}
|
|
|
|
function CGContextScaleCTM(aContext, sx, sy)
|
|
{
|
|
aContext.scale(sx, sy);
|
|
}
|
|
|
|
function CGContextTranslateCTM(aContext, tx, ty)
|
|
{
|
|
aContext.translate(tx, ty);
|
|
}
|
|
|
|
// MODIFICATION: Removed legacy mathematical fallback macros (scale_rotate, rotate_scale, eigen).
|
|
// MODIFICATION: Removed CPFeatureIsCompatible(CPJavaScriptCanvasTransformFeature) branch.
|
|
// Modern target environments inherently support native canvas transformations.
|
|
|
|
function CGContextConcatCTM(aContext, anAffineTransform)
|
|
{
|
|
aContext.transform(anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty);
|
|
}
|
|
|
|
function CGContextDrawImage(aContext, aRect, anImage)
|
|
{
|
|
aContext.drawImage(anImage._image, CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function to_string(aColor)
|
|
{
|
|
// MODIFICATION: Replaced macro ROUND with standard Math.round.
|
|
return "rgba(" + Math.round(aColor.components[0] * 255) + ", " + Math.round(aColor.components[1] * 255) + ", " + Math.round(255 * aColor.components[2]) + ", " + aColor.components[3] + ")";
|
|
}
|
|
|
|
function CGContextDrawLinearGradient(aContext, aGradient, aStartPoint, anEndPoint, options)
|
|
{
|
|
const colors = aGradient.colors;
|
|
const linearGradient = aContext.createLinearGradient(aStartPoint.x, aStartPoint.y, anEndPoint.x, anEndPoint.y);
|
|
let count = colors.length;
|
|
|
|
while (count--)
|
|
{
|
|
linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count]));
|
|
}
|
|
|
|
aContext.fillStyle = linearGradient;
|
|
aContext.fill();
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGContextDrawRadialGradient(aContext, aGradient, aStartCenter, aStartRadius, anEndCenter, anEndRadius, options)
|
|
{
|
|
const colors = aGradient.colors;
|
|
const linearGradient = aContext.createRadialGradient(aStartCenter.x, aStartCenter.y, aStartRadius, anEndCenter.x, anEndCenter.y, anEndRadius);
|
|
let count = colors.length;
|
|
|
|
while (count--)
|
|
{
|
|
linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count]));
|
|
}
|
|
|
|
aContext.fillStyle = linearGradient;
|
|
aContext.fill();
|
|
aContext.hasPath = NO;
|
|
}
|
|
|
|
function CGBitmapGraphicsContextCreate()
|
|
{
|
|
const DOMElement = document.createElement("canvas");
|
|
const context = DOMElement.getContext("2d");
|
|
|
|
context.DOMElement = DOMElement;
|
|
|
|
// canvas gives us no way to query whether the path is empty or not, so we have to track it ourselves
|
|
context.hasPath = NO;
|
|
|
|
return context;
|
|
}
|